1package main
2
3import (
4	"bytes"
5	"errors"
6	"fmt"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"strings"
11	"time"
12	"unicode"
13
14	"github.com/cyrus-and/gdb"
15	"github.com/xyproto/mode"
16	"github.com/xyproto/vt100"
17)
18
19// Editor represents the contents and editor settings, but not settings related to the viewport or scrolling
20type Editor struct {
21	lines              map[int][]rune        // the contents of the current document
22	changed            bool                  // has the contents changed, since last save?
23	tabsSpaces         mode.TabsSpaces       // spaces or tabs, and how many spaces per tab character
24	syntaxHighlight    bool                  // syntax highlighting
25	rainbowParenthesis bool                  // rainbow parenthesis
26	pos                Position              // the current cursor and scroll position
27	searchTerm         string                // the current search term, used when searching
28	stickySearchTerm   string                // for going to the next match with ctrl-n, unless esc has been pressed
29	redraw             bool                  // if the contents should be redrawn in the next loop
30	redrawCursor       bool                  // if the cursor should be moved to the location it is supposed to be
31	lineBeforeSearch   LineIndex             // save the current line when jumping between search results
32	wrapWidth          int                   // set to 80 or 100 to trigger word wrap when typing to that column
33	mode               mode.Mode             // a filetype mode, like for git or markdown
34	filename           string                // the current filename
35	locationHistory    map[string]LineNumber // location history, for jumping to the last location when opening a file
36	quit               bool                  // for indicating if the user wants to end the editor session
37	clearOnQuit        bool                  // clear the terminal when quitting the editor, or not
38	wrapWhenTyping     bool                  // wrap text at a certain limit when typing
39	slowLoad           bool                  // was the initial file slow to load? (might be an indication of a slow disk or USB stick)
40	readOnly           bool                  // is the file read-only when initializing o?
41	sameFilePortal     *Portal               // a portal that points to the same file
42	sshMode            bool                  // is o used over ssh, tmux or screen, in a way that usually requires extra redrawing?
43	debugMode          bool                  // in a mode where ctrl-b toggles breakpoints, ctrl-n steps to the next line and ctrl-space runs the application
44	statusMode         bool                  // display a status line at all times at the bottom of the screen
45	gdb                *gdb.Gdb              // connection to gdb, if debugMode is enabled
46	previousX          int                   // previous cursor position
47	previousY          int                   // previous cursor position
48	Theme                                    // editor theme, embedded struct
49}
50
51// NewCustomEditor takes:
52// * the number of spaces per tab (typically 2, 4 or 8)
53// * if the text should be syntax highlighted
54// * if rainbow parenthesis should be enabled
55// * if text edit mode is enabled (as opposed to "ASCII draw mode")
56// * the current scroll speed, in lines
57// * the following colors:
58//    - text foreground
59//    - text background
60//    - search highlight
61//    - multiline comment
62// * a syntax highlighting scheme
63// * a file mode
64func NewCustomEditor(tabsSpaces mode.TabsSpaces, rainbowParenthesis bool, scrollSpeed int, m mode.Mode, theme Theme, syntaxHighlight bool) *Editor {
65	e := &Editor{}
66	e.SetTheme(theme)
67	e.lines = make(map[int][]rune)
68	e.tabsSpaces = tabsSpaces
69	e.syntaxHighlight = syntaxHighlight
70	e.rainbowParenthesis = rainbowParenthesis
71	p := NewPosition(scrollSpeed)
72	e.pos = *p
73	// If the file is not to be highlighted, set word wrap to 99 (0 to disable)
74	if e.syntaxHighlight {
75		e.wrapWidth = 99
76		e.wrapWhenTyping = false
77	}
78	switch m {
79	case mode.Git:
80		// The subject should ideally be maximum 50 characters long, then the body of the
81		// git commit message can be 72 characters long. Because e-mail standards.
82		e.wrapWidth = 72
83		e.wrapWhenTyping = true
84	case mode.Markdown, mode.Text, mode.Blank:
85		e.wrapWidth = 99
86		e.wrapWhenTyping = false
87	}
88	e.mode = m
89	return e
90}
91
92// NewSimpleEditor return a new simple editor, where the settings are 4 spaces per tab, white text on black background,
93// no syntax highlighting, text edit mode (as opposed to ASCII draw mode), scroll 1 line at a time, color
94// search results magenta, use the default syntax highlighting scheme, don't use git mode and don't use markdown mode,
95// then set the word wrap limit at the given column width.
96func NewSimpleEditor(wordWrapLimit int) *Editor {
97	t := NewDefaultTheme()
98	e := NewCustomEditor(mode.DefaultTabsSpaces, false, 1, mode.Blank, t, false)
99	e.wrapWidth = wordWrapLimit
100	e.wrapWhenTyping = true
101	return e
102}
103
104// CopyLines will create a new map[int][]rune struct that is the copy of all the lines in the editor
105func (e *Editor) CopyLines() map[int][]rune {
106	lines2 := make(map[int][]rune)
107	for key, runes := range e.lines {
108		runes2 := make([]rune, len(runes))
109		copy(runes2, runes)
110		lines2[key] = runes2
111	}
112	return lines2
113}
114
115// Set will store a rune in the editor data, at the given data coordinates
116func (e *Editor) Set(x int, index LineIndex, r rune) {
117	y := int(index)
118	if e.lines == nil {
119		e.lines = make(map[int][]rune)
120	}
121	_, ok := e.lines[y]
122	if !ok {
123		e.lines[y] = make([]rune, 0, x+1)
124	}
125	l := len(e.lines[y])
126	if x < l {
127		e.lines[y][x] = r
128		e.changed = true
129		return
130	}
131	// If the line is too short, fill it up with spaces
132	if l <= x {
133		n := (x + 1) - l
134		e.lines[y] = append(e.lines[y], []rune(strings.Repeat(" ", n))...)
135	}
136
137	// Set the rune
138	e.lines[y][x] = r
139	e.changed = true
140}
141
142// Get will retrieve a rune from the editor data, at the given coordinates
143func (e *Editor) Get(x int, y LineIndex) rune {
144	if e.lines == nil {
145		return ' '
146	}
147	runes, ok := e.lines[int(y)]
148	if !ok {
149		return ' '
150	}
151	if x >= len(runes) {
152		return ' '
153	}
154	return runes[x]
155}
156
157// Changed will return true if the contents were changed since last time this function was called
158func (e *Editor) Changed() bool {
159	return e.changed
160}
161
162// Line returns the contents of line number N, counting from 0
163func (e *Editor) Line(n LineIndex) string {
164	line, ok := e.lines[int(n)]
165	if ok {
166		var sb strings.Builder
167		for _, r := range line {
168			sb.WriteRune(r)
169		}
170		return sb.String()
171	}
172	return ""
173}
174
175// ScreenLine returns the screen contents of line number N, counting from 0.
176// The tabs are expanded.
177func (e *Editor) ScreenLine(n int) string {
178	line, ok := e.lines[n]
179	if ok {
180		var sb strings.Builder
181		skipX := e.pos.offsetX
182		for _, r := range line {
183			if skipX > 0 {
184				skipX--
185				continue
186			}
187			sb.WriteRune(r)
188		}
189		tabSpace := strings.Repeat("\t", e.tabsSpaces.PerTab)
190		return strings.Replace(sb.String(), "\t", tabSpace, -1)
191	}
192	return ""
193}
194
195// LastDataPosition returns the last X index for this line, for the data (does not expand tabs)
196// Can be negative, if the line is empty.
197func (e *Editor) LastDataPosition(n LineIndex) int {
198	return len([]rune(e.Line(n))) - 1
199}
200
201// LastScreenPosition returns the last X index for this line, for the screen (expands tabs)
202// Can be negative, if the line is empty.
203func (e *Editor) LastScreenPosition(n LineIndex) int {
204	extraSpaceBecauseOfTabs := int(e.CountRune('\t', n) * (e.tabsSpaces.PerTab - 1))
205	return (e.LastDataPosition(n) + extraSpaceBecauseOfTabs)
206}
207
208// LastTextPosition returns the last X index for this line, regardless of horizontal scrolling.
209// Can be negative if the line is empty. Tabs are expanded.
210func (e *Editor) LastTextPosition(n LineIndex) int {
211	extraSpaceBecauseOfTabs := int(e.CountRune('\t', n) * (e.tabsSpaces.PerTab - 1))
212	return (e.LastDataPosition(n) + extraSpaceBecauseOfTabs)
213}
214
215// FirstScreenPosition returns the first X index for this line, that is not '\t' or ' '.
216// Does not deal with the X offset.
217func (e *Editor) FirstScreenPosition(n LineIndex) uint {
218	var (
219		counter      uint
220		spacesPerTab = uint(e.tabsSpaces.PerTab)
221	)
222	for _, r := range e.Line(n) {
223		if r == '\t' {
224			counter += spacesPerTab
225		} else if r == ' ' {
226			counter++
227		} else {
228			break
229		}
230	}
231	return counter
232}
233
234// FirstDataPosition returns the first X index for this line, that is not whitespace.
235func (e *Editor) FirstDataPosition(n LineIndex) int {
236	counter := 0
237	for _, r := range e.Line(n) {
238		if !unicode.IsSpace(r) {
239			break
240		}
241		counter++
242	}
243	return counter
244}
245
246// CountRune will count the number of instances of the rune r in the line n
247func (e *Editor) CountRune(r rune, n LineIndex) int {
248	var counter int
249	line, ok := e.lines[int(n)]
250	if ok {
251		for _, l := range line {
252			if l == r {
253				counter++
254			}
255		}
256	}
257	return counter
258}
259
260// Len returns the number of lines
261func (e *Editor) Len() int {
262	maxy := 0
263	for y := range e.lines {
264		if y > maxy {
265			maxy = y
266		}
267	}
268	return maxy + 1
269}
270
271// String returns the contents of the editor
272func (e *Editor) String() string {
273	var sb strings.Builder
274	l := e.Len()
275	for i := 0; i < l; i++ {
276		sb.WriteString(e.Line(LineIndex(i)) + "\n")
277	}
278	return sb.String()
279}
280
281// Clear removes all data from the editor
282func (e *Editor) Clear() {
283	e.lines = make(map[int][]rune)
284	e.changed = true
285}
286
287// Load will try to load a file. The file is assumed to be checked to already exist.
288// Returns a warning message (possibly empty) and an error type
289func (e *Editor) Load(c *vt100.Canvas, tty *vt100.TTY, filename string) (string, error) {
290	var (
291		message string
292		data    []byte
293		err     error
294	)
295
296	// Start a spinner, in a short while
297	quitChan := Spinner(c, tty, fmt.Sprintf("Reading %s... ", filename), fmt.Sprintf("reading %s: stopped by user", filename), 200*time.Millisecond, e.ItalicsColor)
298
299	defer func() {
300		// Stop the spinner
301		quitChan <- true
302	}()
303
304	start := time.Now()
305
306	// Check if the file extension is ".class" and if "jad" is installed
307	if filepath.Ext(filename) == ".class" && which("jad") != "" {
308		if data, err = e.LoadClass(filename); err != nil {
309			return "Could not run jad", err
310		}
311	} else {
312		// Read the file and check if it could be read
313		data, err = ioutil.ReadFile(filename)
314		if err != nil {
315			return message, err
316		}
317	}
318
319	// If enough time passed so that the spinner was shown by now, enter "slow disk mode" where fewer disk-related I/O operations will be performed
320	e.slowLoad = time.Since(start) > 400*time.Millisecond
321
322	// Opinonated replacements
323	data = opinionatedByteReplacer(data)
324
325	// Load the data
326	e.LoadBytes(data)
327
328	// Mark the data as "not changed"
329	e.changed = false
330
331	return message, nil
332}
333
334// LoadBytes replaces the current editor contents with the given bytes
335func (e *Editor) LoadBytes(data []byte) {
336	e.Clear()
337
338	byteLines := bytes.Split(data, []byte{'\n'})
339
340	lb := len(byteLines)
341
342	// If the last line is empty, skip it
343	if len(byteLines) > 0 && len(byteLines[lb-1]) == 0 {
344		byteLines = byteLines[:lb-1]
345		lb--
346	}
347
348	// One allocation for all the lines
349	e.lines = make(map[int][]rune, lb)
350
351	// Place the lines into the editor
352	for y, byteLine := range byteLines {
353		e.lines[y] = []rune(string(byteLine))
354	}
355
356	// Mark the editor contents as "changed"
357	e.changed = true
358}
359
360// PrepareEmpty prepares an empty textual representation of a given filename.
361// If it's an image, there will be text placeholders for pixels.
362// If it's anything else, it will just be blank.
363// Returns an editor mode and an error type.
364func (e *Editor) PrepareEmpty(c *vt100.Canvas, tty *vt100.TTY, filename string) (mode.Mode, error) {
365	var (
366		m    mode.Mode = mode.Blank
367		data []byte
368		err  error
369	)
370
371	// Check if the data could be prepared
372	if err != nil {
373		return m, err
374	}
375
376	lines := strings.Split(string(data), "\n")
377	e.Clear()
378	for y, line := range lines {
379		counter := 0
380		for _, letter := range line {
381			e.Set(counter, LineIndex(y), letter)
382			counter++
383		}
384	}
385	// Mark the data as "not changed"
386	e.changed = false
387
388	return m, nil
389}
390
391// Save will try to save the current editor contents to file.
392// It needs a canvas in case trailing spaces are stripped and the cursor needs to move to the end.
393func (e *Editor) Save(c *vt100.Canvas, tty *vt100.TTY) error {
394
395	// Save the current position
396	bookmark := e.pos.Copy()
397
398	// Strip trailing spaces on all lines
399	l := e.Len()
400	changed := false
401	for i := 0; i < l; i++ {
402		if e.TrimRight(LineIndex(i)) {
403			changed = true
404		}
405	}
406
407	// Trim away trailing whitespace
408	s := strings.TrimRightFunc(e.String(), unicode.IsSpace)
409
410	// Make additional replacements, and add a final newline
411	s = opinionatedStringReplacer.Replace(s) + "\n"
412
413	// TODO: Auto-detect tabs/spaces instead of per-language assumptions
414	if e.mode.Spaces() {
415		// NOTE: This is a hack, that can only replace 10 levels deep.
416		for level := 10; level > 0; level-- {
417			fromString := "\n" + strings.Repeat("\t", level)
418			toString := "\n" + strings.Repeat(" ", level*e.tabsSpaces.PerTab)
419			s = strings.Replace(s, fromString, toString, -1)
420		}
421	}
422
423	// Should the file be saved with the executable bit enabled?
424	// (Does it either start with a shebang or reside in a common bin directory like /usr/bin?)
425	shebang := aBinDirectory(e.filename) || strings.HasPrefix(s, "#!")
426
427	// Mark the data as "not changed"
428	e.changed = false
429
430	// Default file mode (0644 for regular files, 0755 for executable files)
431	var fileMode os.FileMode = 0644
432
433	// Checking the syntax highlighting makes it easy to press `ctrl-t` before saving a script,
434	// to toggle the executable bit on or off. This is only for files that start with "#!".
435	// Also, if the file is in one of the common bin directories, like "/usr/bin", then assume that it
436	// is supposed to be executable.
437	if shebang && e.syntaxHighlight {
438		// This is both a script file and the syntax highlight is enabled.
439		fileMode = 0755
440	}
441
442	// Start a spinner, in a short while
443	quitChan := Spinner(c, tty, fmt.Sprintf("Saving %s... ", e.filename), fmt.Sprintf("saving %s: stopped by user", e.filename), 200*time.Millisecond, e.ItalicsColor)
444
445	// Save the file and return any errors
446	if err := ioutil.WriteFile(e.filename, []byte(s), fileMode); err != nil {
447		// Stop the spinner and return
448		quitChan <- true
449		return err
450	}
451
452	// Apparently, this file isn't read-only, since saving went fine
453	e.readOnly = false
454
455	// "chmod +x" or "chmod -x". This is needed after saving the file, in order to toggle the executable bit.
456	// rust source may start with something like "#![feature(core_intrinsics)]", so avoid that.
457	if shebang && e.mode != mode.Rust && !e.readOnly {
458		// Call Chmod, but ignore errors (since this is just a bonus and not critical)
459		os.Chmod(e.filename, fileMode)
460		e.syntaxHighlight = true
461	}
462
463	// Stop the spinner
464	quitChan <- true
465
466	e.redrawCursor = true
467
468	// Trailing spaces may be trimmed, so move to the end, if needed
469	if changed {
470		e.GoToPosition(c, nil, *bookmark)
471		if e.AfterEndOfLine() {
472			e.EndNoTrim(c)
473		}
474		// Do the redraw manually before showing the status message
475		respectOffset := true
476		redrawCanvas := false
477		e.DrawLines(c, respectOffset, redrawCanvas)
478		e.redraw = false
479	}
480
481	// All done
482	return nil
483}
484
485// TrimRight will remove whitespace from the end of the given line number
486// Returns true if the line was trimmed
487func (e *Editor) TrimRight(index LineIndex) bool {
488	changed := false
489	n := int(index)
490	if line, ok := e.lines[n]; ok {
491		newRunes := []rune(strings.TrimRightFunc(string(line), unicode.IsSpace))
492		// TODO: Just compare lengths instead of contents?
493		if string(newRunes) != string(line) {
494			e.lines[n] = newRunes
495			changed = true
496		}
497	}
498	return changed
499}
500
501// TrimLeft will remove whitespace from the start of the given line number
502// Returns true if the line was trimmed
503func (e *Editor) TrimLeft(index LineIndex) bool {
504	changed := false
505	n := int(index)
506	if line, ok := e.lines[n]; ok {
507		newRunes := []rune(strings.TrimLeftFunc(string(line), unicode.IsSpace))
508		// TODO: Just compare lengths instead of contents?
509		if string(newRunes) != string(line) {
510			e.lines[n] = newRunes
511			changed = true
512		}
513	}
514	return changed
515}
516
517// StripSingleLineComment will strip away trailing single-line comments.
518// TODO: Also strip trailing /* ... */ comments
519func (e *Editor) StripSingleLineComment(line string) string {
520	commentMarker := e.SingleLineCommentMarker()
521	if strings.Count(line, commentMarker) == 1 {
522		p := strings.Index(line, commentMarker)
523		return strings.TrimSpace(line[:p])
524	}
525	return line
526}
527
528// DeleteRestOfLine will delete the rest of the line, from the given position
529func (e *Editor) DeleteRestOfLine() {
530	x, err := e.DataX()
531	if err != nil {
532		// position is after the data, do nothing
533		return
534	}
535	y := int(e.DataY())
536	if e.lines == nil {
537		e.lines = make(map[int][]rune)
538	}
539	v, ok := e.lines[y]
540	if !ok {
541		return
542	}
543	if v == nil {
544		e.lines[y] = make([]rune, 0)
545	}
546	if x > len([]rune(e.lines[y])) {
547		return
548	}
549	e.lines[y] = e.lines[y][:x]
550	e.changed = true
551}
552
553// DeleteLine will delete the given line index
554func (e *Editor) DeleteLine(n LineIndex) {
555	if n < 0 {
556		// This should never happen
557		return
558	}
559	lastLineIndex := LineIndex(e.Len() - 1)
560	endOfDocument := n >= lastLineIndex
561	if endOfDocument {
562		// Just delete this line
563		delete(e.lines, int(n))
564		return
565	}
566	// TODO: Rely on the length of the hash map for finding the index instead of
567	//       searching through each line number key.
568	var maxIndex LineIndex
569	found := false
570	for k := range e.lines {
571		if LineIndex(k) > maxIndex {
572			maxIndex = LineIndex(k)
573			found = true
574		}
575	}
576	if !found {
577		// This should never happen
578		return
579	}
580	if _, ok := e.lines[int(maxIndex)]; !ok {
581		// The line numbers and the length of e.lines does not match
582		return
583	}
584	// Shift all lines after y:
585	// shift all lines after n one step closer to n, overwriting e.lines[n]
586	for index := n; index <= (maxIndex - 1); index++ {
587		i := int(index)
588		e.lines[i] = e.lines[i+1]
589	}
590	// Then delete the final item
591	delete(e.lines, int(maxIndex))
592
593	// This changes the document
594	e.changed = true
595
596	// Make sure no lines are nil
597	e.MakeConsistent()
598}
599
600// DeleteLineMoveBookmark will delete the given line index and also move the bookmark if it's after n
601func (e *Editor) DeleteLineMoveBookmark(n LineIndex, bookmark *Position) {
602	if bookmark != nil && bookmark.LineIndex() > n {
603		bookmark.DecY()
604	}
605	e.DeleteLine(n)
606}
607
608// DeleteCurrentLineMoveBookmark will delete the current line and also move the bookmark one up
609// if it's after the current line.
610func (e *Editor) DeleteCurrentLineMoveBookmark(bookmark *Position) {
611	e.DeleteLineMoveBookmark(e.DataY(), bookmark)
612}
613
614// Delete will delete a character at the given position
615func (e *Editor) Delete() {
616	y := int(e.DataY())
617	lineLen := len([]rune(e.lines[y]))
618	if _, ok := e.lines[y]; !ok || lineLen == 0 || (lineLen == 1 && unicode.IsSpace(e.lines[y][0])) {
619		// All keys in the map that are > y should be shifted -1.
620		// This also overwrites e.lines[y].
621		e.DeleteLine(LineIndex(y))
622		e.changed = true
623		return
624	}
625	x, err := e.DataX()
626	if err != nil || x > len([]rune(e.lines[y]))-1 {
627		// on the last index, just use every element but x
628		e.lines[y] = e.lines[y][:x]
629		// check if the next line exists
630		if _, ok := e.lines[y+1]; ok {
631			// then add the contents of the next line, if available
632			nextLine, ok := e.lines[y+1]
633			if ok && len([]rune(nextLine)) > 0 {
634				e.lines[y] = append(e.lines[y], nextLine...)
635				// then delete the next line
636				e.DeleteLine(LineIndex(y + 1))
637			}
638		}
639		e.changed = true
640		return
641	}
642	// Delete just this character
643	e.lines[y] = append(e.lines[y][:x], e.lines[y][x+1:]...)
644
645	e.changed = true
646
647	// Make sure no lines are nil
648	e.MakeConsistent()
649}
650
651// Empty will check if the current editor contents are empty or not.
652// If there's only one line left and it is only whitespace, that will be considered empty as well.
653func (e *Editor) Empty() bool {
654	l := len(e.lines)
655	if l == 0 {
656		return true
657	}
658	if l == 1 {
659		// Regardless of line number key, check the contents of the one remaining trimmed line
660		for _, line := range e.lines {
661			return len(strings.TrimSpace(string(line))) == 0
662		}
663	}
664	// > 1 lines
665	return false
666}
667
668// MakeConsistent creates an empty slice of runes for any empty lines,
669// to make sure that no line number below e.Len() points to a nil map.
670func (e *Editor) MakeConsistent() {
671	// Check if the keys in the map are consistent
672	for i := 0; i < len(e.lines); i++ {
673		if _, found := e.lines[i]; !found {
674			e.lines[i] = make([]rune, 0)
675			e.changed = true
676		}
677	}
678}
679
680// WithinLimit will check if a line is within the word wrap limit,
681// given a Y position.
682func (e *Editor) WithinLimit(y LineIndex) bool {
683	return len(e.lines[int(y)]) < e.wrapWidth
684}
685
686// LastWord will return the last word of a line,
687// given a Y position. Returns an empty string if there is no last word.
688func (e *Editor) LastWord(y int) string {
689	// TODO: Use a faster method
690	words := strings.Fields(strings.TrimSpace(string(e.lines[y])))
691	if len(words) > 0 {
692		return words[len(words)-1]
693	}
694	return ""
695}
696
697// SplitOvershoot will split the line into a first part that is within the
698// word wrap length and a second part that is the overshooting part.
699// y is the line index (y position, counting from 0).
700// isSpace is true if a space has just been inserted on purpose at the current position.
701// returns true if there was a space at the split point.
702func (e *Editor) SplitOvershoot(index LineIndex, isSpace bool) ([]rune, []rune, bool) {
703	hasSpace := false
704
705	y := int(index)
706
707	// Maximum word length to not keep as one word
708	maxDistance := e.wrapWidth / 2
709	if e.WithinLimit(index) {
710		return e.lines[y], make([]rune, 0), false
711	}
712	splitPosition := e.wrapWidth
713	if isSpace {
714		splitPosition, _ = e.DataX()
715	} else {
716		// Starting at the split position, move left until a space is reached (or the start of the line).
717		// If a space is reached, check if it is too far away from n to be used as a split position, or not.
718		spacePosition := -1
719		for i := splitPosition; i >= 0; i-- {
720			if i < len(e.lines[y]) && unicode.IsSpace(e.lines[y][i]) {
721				// Found a space at position i
722				spacePosition = i
723				break
724			}
725		}
726		// Found a better position to split, at a nearby space?
727		if spacePosition != -1 {
728			hasSpace = true
729			distance := splitPosition - spacePosition
730			if distance > maxDistance {
731				// To far away, don't use this as a split point,
732				// stick to the hard split.
733			} else {
734				// Okay, we found a better split point.
735				splitPosition = spacePosition
736			}
737		}
738	}
739
740	// Split the line into two parts
741
742	n := splitPosition
743	// Make space for the two parts
744	first := make([]rune, len(e.lines[y][:n]))
745	second := make([]rune, len(e.lines[y][n:]))
746	// Copy the line into first and second
747	copy(first, e.lines[y][:n])
748	copy(second, e.lines[y][n:])
749
750	// If the second part starts with a space, remove it
751	if len(second) > 0 && unicode.IsSpace(second[0]) {
752		second = second[1:]
753		hasSpace = true
754	}
755
756	return first, second, hasSpace
757}
758
759// WrapAllLinesAt will word wrap all lines that are longer than n,
760// with a maximum overshoot of too long words (measured in runes) of maxOvershoot.
761// Returns true if any lines were wrapped.
762func (e *Editor) WrapAllLinesAt(n, maxOvershoot int) bool {
763	// This is not even called when the problematic insert behavior occurs
764
765	wrapped := false
766	insertedLines := 0
767
768	y := e.DataY()
769
770	for i := 0; i < e.Len(); i++ {
771		if e.WithinLimit(LineIndex(i)) {
772			continue
773		}
774		wrapped = true
775
776		first, second, spaceBetween := e.SplitOvershoot(LineIndex(i), false)
777
778		if len(first) > 0 && len(second) > 0 {
779
780			e.lines[i] = first
781			if spaceBetween {
782				second = append(second, ' ')
783			}
784			e.lines[i+1] = append(second, e.lines[i+1]...)
785			e.InsertLineBelowAt(LineIndex(i + 1))
786
787			// This isn't perfect, but it helps move the cursor somewhere in
788			// the vicinity of where the line was before word wrapping.
789			// TODO: Make the cursor placement exact.
790			if LineIndex(i) < y {
791				insertedLines++
792			}
793
794			e.changed = true
795		}
796	}
797
798	// Move the cursor as well, after wrapping
799	if insertedLines > 0 {
800		e.pos.sy += insertedLines
801		if e.pos.sy < 0 {
802			e.pos.sy = 0
803		} else if e.pos.sy >= len(e.lines) {
804			e.pos.sy = len(e.lines) - 1
805		}
806		e.redraw = true
807		e.redrawCursor = true
808	}
809
810	return wrapped
811}
812
813// InsertLineAbove will attempt to insert a new line above the current position
814func (e *Editor) InsertLineAbove() {
815	lineIndex := e.DataY()
816
817	if e.sameFilePortal != nil {
818		e.sameFilePortal.NewLineInserted(lineIndex)
819	}
820
821	y := int(lineIndex)
822
823	// Create new set of lines
824	lines2 := make(map[int][]rune)
825
826	// If at the first line, just add a line at the top
827	if y == 0 {
828
829		// Insert a blank line
830		lines2[0] = make([]rune, 0)
831		// Then insert all the other lines, shifted by 1
832		for k, v := range e.lines {
833			lines2[k+1] = v
834		}
835		y++
836
837	} else {
838
839		// For each line in the old map, if at (y-1), insert a blank line
840		// (insert a blank line above)
841		for k, v := range e.lines {
842			if k < (y - 1) {
843				lines2[k] = v
844			} else if k == (y - 1) {
845				lines2[k] = v
846				lines2[k+1] = make([]rune, 0)
847			} else if k > (y - 1) {
848				lines2[k+1] = v
849			}
850		}
851
852	}
853
854	// Use the new set of lines
855	e.lines = lines2
856
857	// Make sure no lines are nil
858	e.MakeConsistent()
859
860	// Skip trailing newlines after this line
861	for i := len(e.lines); i > y; i-- {
862		if len([]rune(e.lines[i])) == 0 {
863			delete(e.lines, i)
864		} else {
865			break
866		}
867	}
868	e.changed = true
869}
870
871// InsertLineBelow will attempt to insert a new line below the current position
872func (e *Editor) InsertLineBelow() {
873	lineIndex := e.DataY()
874	if e.sameFilePortal != nil {
875		e.sameFilePortal.NewLineInserted(lineIndex)
876	}
877	e.InsertLineBelowAt(lineIndex)
878}
879
880// InsertLineBelowAt will attempt to insert a new line below the given y position
881func (e *Editor) InsertLineBelowAt(index LineIndex) {
882	y := int(index)
883
884	// Make sure no lines are nil
885	e.MakeConsistent()
886
887	// If we are the the last line, add an empty line at the end and return
888	if y == (len(e.lines) - 1) {
889		e.lines[int(y)+1] = make([]rune, 0)
890		e.changed = true
891		return
892	}
893
894	// Create new set of lines, with room for one more
895	lines2 := make(map[int][]rune, len(e.lines)+1)
896
897	// For each line in the old map, if at y, insert a blank line
898	// (insert a blank line below)
899	for k, v := range e.lines {
900		if k < y {
901			lines2[k] = v
902		} else if k == y {
903			lines2[k] = v
904			lines2[k+1] = make([]rune, 0)
905		} else if k > y {
906			lines2[k+1] = v
907		}
908	}
909	// Use the new set of lines
910	e.lines = lines2
911
912	// Skip trailing newlines after this line
913	for i := len(e.lines); i > y; i-- {
914		if len([]rune(e.lines[i])) == 0 {
915			delete(e.lines, i)
916		} else {
917			break
918		}
919	}
920
921	e.changed = true
922}
923
924// Insert will insert a rune at the given position, with no word wrap,
925// but MakeConsisten will be called.
926func (e *Editor) Insert(r rune) {
927	// Ignore it if the current position is out of bounds
928	x, _ := e.DataX()
929
930	y := int(e.DataY())
931
932	// If there are no lines, initialize and set the 0th rune to the given one
933	if e.lines == nil {
934		e.lines = make(map[int][]rune)
935		e.lines[0] = []rune{r}
936		return
937	}
938
939	// If the current line is empty, initialize it with a line that is just the given rune
940	_, ok := e.lines[y]
941	if !ok {
942		e.lines[y] = []rune{r}
943		return
944	}
945	if len([]rune(e.lines[y])) < x {
946		// Can only insert in the existing block of text
947		return
948	}
949	newlineLength := len(e.lines[y]) + 1
950	newline := make([]rune, newlineLength)
951	for i := 0; i < x; i++ {
952		newline[i] = e.lines[y][i]
953	}
954	newline[x] = r
955	for i := x + 1; i < newlineLength; i++ {
956		newline[i] = e.lines[y][i-1]
957	}
958	e.lines[y] = newline
959
960	e.changed = true
961
962	// Make sure no lines are nil
963	e.MakeConsistent()
964}
965
966// CreateLineIfMissing will create a line at the given Y index, if it's missing
967func (e *Editor) CreateLineIfMissing(n LineIndex) {
968	if e.lines == nil {
969		e.lines = make(map[int][]rune)
970	}
971	_, ok := e.lines[int(n)]
972	if !ok {
973		e.lines[int(n)] = make([]rune, 0)
974		e.changed = true
975	}
976}
977
978// SetColors will set the current editor theme (foreground, background).
979// The background color should be a background attribute (like vt100.BackgroundBlue).
980func (e *Editor) SetColors(fg, bg vt100.AttributeColor) {
981	e.Foreground = fg
982	e.Background = bg
983}
984
985// WordCount returns the number of spaces in the text + 1
986func (e *Editor) WordCount() int {
987	return len(strings.Fields(e.String()))
988}
989
990// ToggleSyntaxHighlight toggles syntax highlighting
991func (e *Editor) ToggleSyntaxHighlight() {
992	e.syntaxHighlight = !e.syntaxHighlight
993}
994
995// ToggleRainbow toggles rainbow parenthesis
996func (e *Editor) ToggleRainbow() {
997	e.rainbowParenthesis = !e.rainbowParenthesis
998}
999
1000// SetRainbow enables or disables rainbow parenthesis
1001func (e *Editor) SetRainbow(rainbowParenthesis bool) {
1002	e.rainbowParenthesis = rainbowParenthesis
1003}
1004
1005// SetLine will fill the given line index with the given string.
1006// Any previous contents of that line is removed.
1007func (e *Editor) SetLine(n LineIndex, s string) {
1008	e.CreateLineIfMissing(n)
1009	e.lines[int(n)] = make([]rune, 0)
1010	counter := 0
1011	// It's important not to use the index value when looping over a string,
1012	// unless the byte index is what one's after, as opposed to the rune index.
1013	for _, letter := range s {
1014		e.Set(counter, n, letter)
1015		counter++
1016	}
1017}
1018
1019// SetCurrentLine will replace the current line with the given string
1020func (e *Editor) SetCurrentLine(s string) {
1021	e.SetLine(e.DataY(), s)
1022}
1023
1024// SplitLine will, at the given position, split the line in two.
1025// The right side of the contents is moved to a new line below.
1026func (e *Editor) SplitLine() bool {
1027	x, err := e.DataX()
1028	if err != nil {
1029		// After contents, this should not happen, do nothing
1030		return false
1031	}
1032
1033	y := e.DataY()
1034
1035	// Get the contents of this line
1036	runeLine := e.lines[int(y)]
1037	if len(runeLine) < 2 {
1038		// Did not split
1039		return false
1040	}
1041	leftContents := strings.TrimRightFunc(string(runeLine[:x]), unicode.IsSpace)
1042	rightContents := string(runeLine[x:])
1043	// Insert a new line above this one
1044	e.InsertLineAbove()
1045	// Replace this line with the left contents
1046	e.SetLine(y, leftContents)
1047	e.SetLine(y+1, rightContents)
1048	// Splitted
1049	return true
1050}
1051
1052// DataX will return the X position in the data (as opposed to the X position in the viewport)
1053func (e *Editor) DataX() (int, error) {
1054	// the y position in the data is the lines scrolled + current screen cursor Y position
1055	dataY := e.pos.offsetY + e.pos.sy
1056	// get the current line of text
1057	screenCounter := 0 // counter for the characters on the screen
1058	// loop, while also keeping track of tab expansion
1059	// add a space to allow to jump to the position after the line and get a valid data position
1060	found := false
1061	dataX := 0
1062	runeCounter := 0
1063	for _, r := range e.lines[dataY] {
1064		// When we reached the correct screen position, use i as the data position
1065		if screenCounter == (e.pos.sx + e.pos.offsetX) {
1066			dataX = runeCounter
1067			found = true
1068			break
1069		}
1070		// Increase the counter, based on the current rune
1071		if r == '\t' {
1072			screenCounter += e.tabsSpaces.PerTab
1073		} else {
1074			screenCounter++
1075		}
1076		runeCounter++
1077	}
1078	if !found {
1079		return runeCounter, errors.New("position is after data")
1080	}
1081	// Return the data cursor
1082	return dataX, nil
1083}
1084
1085// DataY will return the Y position in the data (as opposed to the Y position in the viewport)
1086func (e *Editor) DataY() LineIndex {
1087	return LineIndex(e.pos.offsetY + e.pos.sy)
1088}
1089
1090// SetRune will set a rune at the current data position
1091func (e *Editor) SetRune(r rune) {
1092	// Only set a rune if x is within the current line contents
1093	if x, err := e.DataX(); err == nil {
1094		e.Set(x, e.DataY(), r)
1095	}
1096}
1097
1098// NextLine will go to the start of the next line, with scrolling
1099func (e *Editor) NextLine(y LineIndex, c *vt100.Canvas, status *StatusBar) {
1100	e.pos.sx = 0
1101	e.pos.offsetX = 0
1102	e.GoTo(y+1, c, status)
1103}
1104
1105// InsertBelow will insert the given rune at the start of the line below,
1106// starting a new line if required.
1107func (e *Editor) InsertBelow(y int, r rune) {
1108	if _, ok := e.lines[y+1]; !ok {
1109		// If the next line does not exist, create one containing just "r"
1110		e.lines[y+1] = []rune{r}
1111	} else if len(e.lines[y+1]) > 0 {
1112		// If the next line is non-empty, insert "r" at the start
1113		e.lines[y+1] = append([]rune{r}, e.lines[y+1][:]...)
1114	} else {
1115		// The next line exists, but is of length 0, should not happen, just replace it
1116		e.lines[y+1] = []rune{r}
1117	}
1118}
1119
1120// InsertStringBelow will insert the given string at the start of the line below,
1121// starting a new line if required.
1122func (e *Editor) InsertStringBelow(y int, s string) {
1123	if _, ok := e.lines[y+1]; !ok {
1124		// If the next line does not exist, create one containing the string
1125		e.lines[y+1] = []rune(s)
1126	} else if len(e.lines[y+1]) > 0 {
1127		// If the next line is non-empty, insert the string at the start
1128		e.lines[y+1] = append([]rune(s), e.lines[y+1][:]...)
1129	} else {
1130		// The next line exists, but is of length 0, should not happen, just replace it
1131		e.lines[y+1] = []rune(s)
1132	}
1133}
1134
1135// InsertStringAndMove will insert a string at the current data position
1136// and possibly move down. This will also call e.WriteRune, e.Down and e.Next, as needed.
1137func (e *Editor) InsertStringAndMove(c *vt100.Canvas, s string) {
1138	for _, r := range s {
1139		if r == '\n' {
1140			e.InsertLineBelow()
1141			e.Down(c, nil)
1142			continue
1143		}
1144		e.InsertRune(c, r)
1145		e.WriteRune(c)
1146		e.Next(c)
1147	}
1148}
1149
1150// InsertString will insert a string without newlines at the current data position.
1151// his will also call e.WriteRune and e.Next, as needed.
1152func (e *Editor) InsertString(c *vt100.Canvas, s string) {
1153	for _, r := range s {
1154		e.InsertRune(c, r)
1155		e.WriteRune(c)
1156		e.Next(c)
1157	}
1158}
1159
1160// Rune will get the rune at the current data position
1161func (e *Editor) Rune() rune {
1162	x, err := e.DataX()
1163	if err != nil {
1164		// after line contents, return a zero rune
1165		return rune(0)
1166	}
1167	return e.Get(x, e.DataY())
1168}
1169
1170// LeftRune will get the rune to the left of the current data position
1171func (e *Editor) LeftRune() rune {
1172	y := e.DataY()
1173	x, err := e.DataX()
1174	if err != nil {
1175		// This is after the line contents, return the last rune
1176		runes, ok := e.lines[int(y)]
1177		if !ok || len(runes) == 0 {
1178			return rune(0)
1179		}
1180		// Return the last rune
1181		return runes[len(runes)-1]
1182	}
1183	if x <= 0 {
1184		// Nothing to the left of this
1185		return rune(0)
1186	}
1187	// Return the rune to the left
1188	return e.Get(x-1, e.DataY())
1189}
1190
1191// CurrentLine will get the current data line, as a string
1192func (e *Editor) CurrentLine() string {
1193	return e.Line(e.DataY())
1194}
1195
1196// Home will move the cursor the the start of the line (x = 0)
1197// And also scroll all the way to the left.
1198func (e *Editor) Home() {
1199	e.pos.sx = 0
1200	e.pos.offsetX = 0
1201	e.redraw = true
1202}
1203
1204// End will move the cursor to the position right after the end of the current line contents,
1205// and also trim away whitespace from the right side.
1206func (e *Editor) End(c *vt100.Canvas) {
1207	y := e.DataY()
1208	e.TrimRight(y)
1209	x := e.LastTextPosition(y) + 1
1210	e.pos.SetX(c, x)
1211	e.redraw = true
1212}
1213
1214// EndNoTrim will move the cursor to the position right after the end of the current line contents
1215func (e *Editor) EndNoTrim(c *vt100.Canvas) {
1216	x := e.LastTextPosition(e.DataY()) + 1
1217	e.pos.SetX(c, x)
1218	e.redraw = true
1219}
1220
1221// AtEndOfLine returns true if the cursor is at exactly the last character of the line, not the one after
1222func (e *Editor) AtEndOfLine() bool {
1223	return e.pos.sx+e.pos.offsetX == e.LastTextPosition(e.DataY())
1224}
1225
1226// DownEnd will move down and then choose a "smart" X position
1227func (e *Editor) DownEnd(c *vt100.Canvas) error {
1228	tmpx := e.pos.sx
1229	err := e.pos.Down(c)
1230	if err != nil {
1231		return err
1232	}
1233	line := e.CurrentLine()
1234	if len(strings.TrimSpace(line)) == 1 {
1235		e.TrimRight(e.DataY())
1236		e.End(c)
1237	} else if e.AfterLineScreenContentsPlusOne() && tmpx > 1 {
1238		e.End(c)
1239		if e.pos.sx != tmpx && e.pos.sx > e.pos.savedX {
1240			e.pos.savedX = tmpx
1241		}
1242	} else {
1243		e.pos.sx = e.pos.savedX
1244
1245		if e.pos.sx < 0 {
1246			e.pos.sx = 0
1247		}
1248		if e.AfterLineScreenContentsPlusOne() {
1249			e.End(c)
1250		}
1251
1252		// Also checking if e.Rune() is ' ' is nice for code, but horrible for regular text files
1253		if e.Rune() == '\t' {
1254			e.pos.sx = int(e.FirstScreenPosition(e.DataY()))
1255		}
1256
1257		// Expand the line, then check if e.pos.sx falls on a tab character ("\t" is expanded to several tabs ie. "\t\t\t\t")
1258		expandedRunes := []rune(strings.Replace(line, "\t", strings.Repeat("\t", e.tabsSpaces.PerTab), -1))
1259		if e.pos.sx < len(expandedRunes) && expandedRunes[e.pos.sx] == '\t' {
1260			e.pos.sx = int(e.FirstScreenPosition(e.DataY()))
1261		}
1262	}
1263	return nil
1264}
1265
1266// UpEnd will move up and then choose a "smart" X position
1267func (e *Editor) UpEnd(c *vt100.Canvas) error {
1268	tmpx := e.pos.sx
1269	err := e.pos.Up()
1270	if err != nil {
1271		return err
1272	}
1273	if e.AfterLineScreenContentsPlusOne() && tmpx > 1 {
1274		e.End(c)
1275		if e.pos.sx != tmpx && e.pos.sx > e.pos.savedX {
1276			e.pos.savedX = tmpx
1277		}
1278	} else {
1279		e.pos.sx = e.pos.savedX
1280
1281		if e.pos.sx < 0 {
1282			e.pos.sx = 0
1283		}
1284		if e.AfterLineScreenContentsPlusOne() {
1285			e.End(c)
1286		}
1287
1288		// Also checking if e.Rune() is ' ' is nice for code, but horrible for regular text files
1289		if e.Rune() == '\t' {
1290			e.pos.sx = int(e.FirstScreenPosition(e.DataY()))
1291		}
1292
1293		// Expand the line, then check if e.pos.sx falls on a tab character ("\t" is expanded to several tabs ie. "\t\t\t\t")
1294		expandedRunes := []rune(strings.Replace(e.CurrentLine(), "\t", strings.Repeat("\t", e.tabsSpaces.PerTab), -1))
1295		if e.pos.sx < len(expandedRunes) && expandedRunes[e.pos.sx] == '\t' {
1296			e.pos.sx = int(e.FirstScreenPosition(e.DataY()))
1297		}
1298	}
1299	return nil
1300}
1301
1302// Next will move the cursor to the next position in the contents
1303func (e *Editor) Next(c *vt100.Canvas) error {
1304	// Ignore it if the position is out of bounds
1305	atTab := e.Rune() == '\t'
1306	if atTab {
1307		e.pos.sx += e.tabsSpaces.PerTab
1308	} else {
1309		e.pos.sx++
1310	}
1311	// Did we move too far on this line?
1312	if e.AfterLineScreenContentsPlusOne() {
1313		// Undo the move
1314		if atTab {
1315			e.pos.sx -= e.tabsSpaces.PerTab
1316		} else {
1317			e.pos.sx--
1318		}
1319		// Move down
1320		err := e.pos.Down(c)
1321		if err != nil {
1322			return err
1323		}
1324		// Move to the start of the line
1325		e.pos.sx = 0
1326	}
1327	return nil
1328}
1329
1330// LeftRune2 returns the rune to the left of the current position, or an error
1331func (e *Editor) LeftRune2() (rune, error) {
1332	x, err := e.DataX()
1333	if err != nil {
1334		return rune(0), err
1335	}
1336	x--
1337	if x <= 0 {
1338		return rune(0), errors.New("no runes to the left")
1339	}
1340	return e.Get(x, e.DataY()), nil
1341}
1342
1343// TabToTheLeft returns true if there is a '\t' to the left of the current position
1344func (e *Editor) TabToTheLeft() bool {
1345	r, err := e.LeftRune2()
1346	if err != nil {
1347		return false
1348	}
1349	return r == '\t'
1350}
1351
1352// Prev will move the cursor to the previous position in the contents
1353func (e *Editor) Prev(c *vt100.Canvas) error {
1354
1355	atTab := e.TabToTheLeft() || (e.pos.sx <= e.tabsSpaces.PerTab && e.Get(0, e.DataY()) == '\t')
1356	if e.pos.sx == 0 && e.pos.offsetX > 0 {
1357		// at left edge, but can scroll to the left
1358		e.pos.offsetX--
1359		e.redraw = true
1360	} else {
1361		// If at a tab character, move a few more positions
1362		if atTab {
1363			e.pos.sx -= e.tabsSpaces.PerTab
1364		} else {
1365			e.pos.sx--
1366		}
1367	}
1368	if e.pos.sx < 0 { // Did we move too far and there is no X offset?
1369		// Undo the move
1370		if atTab {
1371			e.pos.sx += e.tabsSpaces.PerTab
1372		} else {
1373			e.pos.sx++
1374		}
1375		// Move up, and to the end of the line above, if in EOL mode
1376		err := e.pos.Up()
1377		if err != nil {
1378			return err
1379		}
1380		e.End(c)
1381	}
1382	return nil
1383}
1384
1385// Right will move the cursor to the right, if possible.
1386// It will not move the cursor up or down.
1387func (p *Position) Right(c *vt100.Canvas) {
1388	w := 80 // default width
1389	if c != nil {
1390		w = int(c.Width())
1391	}
1392	if p.sx < (w - 1) {
1393		p.sx++
1394	} else {
1395		p.sx = 0
1396		p.offsetX += (w - 1)
1397	}
1398}
1399
1400// Left will move the cursor to the left, if possible.
1401// It will not move the cursor up or down.
1402func (p *Position) Left() {
1403	if p.sx > 0 {
1404		p.sx--
1405	}
1406}
1407
1408// SaveX will save the current X position, if it's within reason
1409func (e *Editor) SaveX(regardless bool) {
1410	if regardless || (!e.AfterLineScreenContentsPlusOne() && e.pos.sx > 1) {
1411		e.pos.savedX = e.pos.sx
1412	}
1413}
1414
1415// ScrollDown will scroll down the given amount of lines given in scrollSpeed
1416func (e *Editor) ScrollDown(c *vt100.Canvas, status *StatusBar, scrollSpeed int) bool {
1417	// Find out if we can scroll scrollSpeed, or less
1418	canScroll := scrollSpeed
1419
1420	// Last y position in the canvas
1421	canvasLastY := int(c.H() - 1)
1422
1423	// Retrieve the current editor scroll offset offset
1424	mut.RLock()
1425	offset := e.pos.offsetY
1426	mut.RUnlock()
1427
1428	// Number of lines in the document
1429	l := e.Len()
1430
1431	if offset >= l-canvasLastY {
1432		c.Draw()
1433		// Don't redraw
1434		return false
1435	}
1436	if status != nil {
1437		status.Clear(c)
1438	}
1439	if (offset + canScroll) >= (l - canvasLastY) {
1440		// Almost at the bottom, we can scroll the remaining lines
1441		canScroll = (l - canvasLastY) - offset
1442	}
1443
1444	// Move the scroll offset
1445	mut.Lock()
1446	e.pos.offsetX = 0
1447	e.pos.offsetY += canScroll
1448	mut.Unlock()
1449
1450	// Prepare to redraw
1451	return true
1452}
1453
1454// ScrollUp will scroll down the given amount of lines given in scrollSpeed
1455func (e *Editor) ScrollUp(c *vt100.Canvas, status *StatusBar, scrollSpeed int) bool {
1456	// Find out if we can scroll scrollSpeed, or less
1457	canScroll := scrollSpeed
1458
1459	// Retrieve the current editor scroll offset offset
1460	mut.RLock()
1461	offset := e.pos.offsetY
1462	mut.RUnlock()
1463
1464	if offset == 0 {
1465		// Can't scroll further up
1466		// Status message
1467		//status.SetMessage("Start of text")
1468		//status.Show(c, p)
1469		//c.Draw()
1470		// Redraw
1471		return true
1472	}
1473	if status != nil {
1474		status.Clear(c)
1475	}
1476	if offset-canScroll < 0 {
1477		// Almost at the top, we can scroll the remaining lines
1478		canScroll = offset
1479	}
1480	// Move the scroll offset
1481	mut.Lock()
1482	e.pos.offsetX = 0
1483	e.pos.offsetY -= canScroll
1484	mut.Unlock()
1485	// Prepare to redraw
1486	return true
1487}
1488
1489// AtFirstLineOfDocument is true if we're at the first line of the document
1490func (e *Editor) AtFirstLineOfDocument() bool {
1491	return e.DataY() == LineIndex(0)
1492}
1493
1494// AtLastLineOfDocument is true if we're at the last line of the document
1495func (e *Editor) AtLastLineOfDocument() bool {
1496	return e.DataY() == LineIndex(e.Len()-1)
1497}
1498
1499// AfterLastLineOfDocument is true if we're after the last line of the document
1500func (e *Editor) AfterLastLineOfDocument() bool {
1501	return e.DataY() > LineIndex(e.Len()-1)
1502}
1503
1504// AtOrAfterLastLineOfDocument is true if we're at or after the last line of the document
1505func (e *Editor) AtOrAfterLastLineOfDocument() bool {
1506	return e.DataY() >= LineIndex(e.Len()-1)
1507}
1508
1509// AtOrAfterEndOfDocument is true if the cursor is at or after the end of the last line of the document
1510func (e *Editor) AtOrAfterEndOfDocument() bool {
1511	return (e.AtLastLineOfDocument() && e.AtOrAfterEndOfLine()) || e.AfterLastLineOfDocument()
1512}
1513
1514// AfterEndOfDocument is true if the cursor is after the end of the last line of the document
1515func (e *Editor) AfterEndOfDocument() bool {
1516	return e.AfterLastLineOfDocument() // && e.AtOrAfterEndOfLine()
1517}
1518
1519// AtEndOfDocument is true if the cursor is at the end of the last line of the document
1520func (e *Editor) AtEndOfDocument() bool {
1521	return e.AtLastLineOfDocument() && e.AtEndOfLine()
1522}
1523
1524// AtStartOfDocument is true if we're at the first line of the document
1525func (e *Editor) AtStartOfDocument() bool {
1526	return e.pos.sy == 0 && e.pos.offsetY == 0
1527}
1528
1529// AtStartOfScreenLine is true if the cursor is a the start of the screen line.
1530// The line may be scrolled all the way to the end, and the cursor moved to the left of the screen, for instance.
1531func (e *Editor) AtStartOfScreenLine() bool {
1532	return e.pos.AtStartOfScreenLine()
1533}
1534
1535// AtStartOfTheLine is true if the cursor is a the start of the screen line, and the line is not scrolled.
1536func (e *Editor) AtStartOfTheLine() bool {
1537	return e.pos.AtStartOfTheLine()
1538}
1539
1540// AtLeftEdgeOfDocument is true if we're at the first column at the document. Same as AtStarOfTheLine.
1541func (e *Editor) AtLeftEdgeOfDocument() bool {
1542	return e.pos.sx == 0 && e.pos.offsetX == 0
1543}
1544
1545// AtOrAfterEndOfLine returns true if the cursor is at or after the contents of this line
1546func (e *Editor) AtOrAfterEndOfLine() bool {
1547	if e.EmptyLine() {
1548		return true
1549	}
1550	x, err := e.DataX()
1551	if err != nil {
1552		// After end of data
1553		return true
1554	}
1555	return x >= e.LastDataPosition(e.DataY())
1556}
1557
1558// AfterEndOfLine returns true if the cursor is after the contents of this line
1559func (e *Editor) AfterEndOfLine() bool {
1560	if e.EmptyLine() {
1561		return true
1562	}
1563	x, err := e.DataX()
1564	if err != nil {
1565		// After end of data
1566		return true
1567	}
1568	return x > e.LastDataPosition(e.DataY())
1569}
1570
1571// AfterLineScreenContents will check if the cursor is after the current line contents
1572func (e *Editor) AfterLineScreenContents() bool {
1573	return e.pos.sx > e.LastScreenPosition(e.DataY())
1574}
1575
1576// AfterScreenWidth checks if the current cursor position has moved after the terminal/canvas width
1577func (e *Editor) AfterScreenWidth(c *vt100.Canvas) bool {
1578	w := 80 // default width
1579	if c != nil {
1580		w = int(c.W())
1581	}
1582	return e.pos.sx >= w
1583}
1584
1585// AfterLineScreenContentsPlusOne will check if the cursor is after the current line contents, with a margin of 1
1586func (e *Editor) AfterLineScreenContentsPlusOne() bool {
1587	return e.pos.sx > (e.LastScreenPosition(e.DataY()) + 1)
1588}
1589
1590// WriteRune writes the current rune to the given canvas
1591func (e *Editor) WriteRune(c *vt100.Canvas) {
1592	if c != nil {
1593		c.WriteRune(uint(e.pos.sx+e.pos.offsetX), uint(e.pos.sy), e.Foreground, e.Background, e.Rune())
1594	}
1595}
1596
1597// WriteTab writes spaces when there is a tab character, to the canvas
1598func (e *Editor) WriteTab(c *vt100.Canvas) {
1599	spacesPerTab := e.tabsSpaces.PerTab
1600	for x := e.pos.sx; x < e.pos.sx+spacesPerTab; x++ {
1601		c.WriteRune(uint(x+e.pos.offsetX), uint(e.pos.sy), e.Foreground, e.Background, ' ')
1602	}
1603}
1604
1605// EmptyRightTrimmedLine checks if the current line is empty (and whitespace doesn't count)
1606func (e *Editor) EmptyRightTrimmedLine() bool {
1607	return len(strings.TrimRightFunc(e.CurrentLine(), unicode.IsSpace)) == 0
1608}
1609
1610// EmptyRightTrimmedLineBelow checks if the next line is empty (and whitespace doesn't count)
1611func (e *Editor) EmptyRightTrimmedLineBelow() bool {
1612	return len(strings.TrimRightFunc(e.Line(e.DataY()+1), unicode.IsSpace)) == 0
1613}
1614
1615// EmptyLine returns true if the current line is completely empty, no whitespace or anything
1616func (e *Editor) EmptyLine() bool {
1617	return len(e.CurrentLine()) == 0
1618}
1619
1620// EmptyTrimmedLine returns true if the current line (trimmed) is completely empty
1621func (e *Editor) EmptyTrimmedLine() bool {
1622	return len(e.TrimmedLine()) == 0
1623}
1624
1625// AtStartOfTextScreenLine returns true if the position is at the start of the text for this screen line
1626func (e *Editor) AtStartOfTextScreenLine() bool {
1627	return uint(e.pos.sx) == e.FirstScreenPosition(e.DataY())
1628}
1629
1630// BeforeStartOfTextScreenLine returns true if the position is before the start of the text for this screen line
1631func (e *Editor) BeforeStartOfTextScreenLine() bool {
1632	return uint(e.pos.sx) < e.FirstScreenPosition(e.DataY())
1633}
1634
1635// AtOrBeforeStartOfTextScreenLine returns true if the position is before or at the start of the text for this screen line
1636func (e *Editor) AtOrBeforeStartOfTextScreenLine() bool {
1637	return uint(e.pos.sx) <= e.FirstScreenPosition(e.DataY())
1638}
1639
1640// GoTo will go to a given line index, counting from 0
1641// Returns true if the editor should be redrawn
1642// status is used for clearing status bar messages and can be nil
1643func (e *Editor) GoTo(dataY LineIndex, c *vt100.Canvas, status *StatusBar) bool {
1644	if dataY == e.DataY() {
1645		// Already at the correct line, but still trigger a redraw
1646		return true
1647	}
1648	reachedEnd := false
1649	// Out of bounds checking for y
1650	if dataY < 0 {
1651		dataY = 0
1652	} else if dataY >= LineIndex(e.Len()) {
1653		dataY = LineIndex(e.Len() - 1)
1654		reachedEnd = true
1655	}
1656
1657	h := 25
1658	if c != nil {
1659		// Get the current terminal height
1660		h = int(c.Height())
1661	}
1662
1663	// Is the place we want to go within the current scroll window?
1664	topY := LineIndex(e.pos.offsetY)
1665	botY := LineIndex(e.pos.offsetY + h)
1666
1667	if dataY >= topY && dataY < botY {
1668		// No scrolling is needed, just move the screen y position
1669		e.pos.sy = int(dataY) - e.pos.offsetY
1670		if e.pos.sy < 0 {
1671			e.pos.sy = 0
1672		}
1673	} else if int(dataY) < h {
1674		// No scrolling is needed, just move the screen y position
1675		e.pos.offsetY = 0
1676		e.pos.sy = int(dataY)
1677		if e.pos.sy < 0 {
1678			e.pos.sy = 0
1679		}
1680	} else if reachedEnd {
1681		// To the end of the text
1682		e.pos.offsetY = e.Len() - h
1683		e.pos.sy = h - 1
1684	} else {
1685		prevY := e.pos.sy
1686		// Scrolling is needed
1687		e.pos.sy = 0
1688		e.pos.offsetY = int(dataY)
1689		lessJumpY := prevY
1690		lessJumpOffset := int(dataY) - prevY
1691		if (lessJumpY + lessJumpOffset) < e.Len() {
1692			e.pos.sy = lessJumpY
1693			e.pos.offsetY = lessJumpOffset
1694		}
1695	}
1696
1697	// The Y scrolling is done, move the X position according to the contents of the line
1698	e.pos.SetX(c, int(e.FirstScreenPosition(e.DataY())))
1699
1700	// Clear all status messages
1701	if status != nil {
1702		status.ClearAll(c)
1703	}
1704
1705	// Trigger cursor redraw
1706	e.redrawCursor = true
1707
1708	// Should also redraw the text
1709	return true
1710}
1711
1712// GoToLineNumber will go to a given line number, but counting from 1, not from 0!
1713func (e *Editor) GoToLineNumber(lineNumber LineNumber, c *vt100.Canvas, status *StatusBar, center bool) bool {
1714	if lineNumber < 1 {
1715		lineNumber = 1
1716	}
1717	redraw := e.GoTo(lineNumber.LineIndex(), c, status)
1718	if redraw && center {
1719		e.Center(c)
1720	}
1721	return redraw
1722}
1723
1724// GoToLineNumberAndCol will go to a given line number and column number, but counting from 1, not from 0!
1725func (e *Editor) GoToLineNumberAndCol(lineNumber LineNumber, colNumber ColNumber, c *vt100.Canvas, status *StatusBar, center bool) bool {
1726	if colNumber < 1 {
1727		colNumber = 1
1728	}
1729	if lineNumber < 1 {
1730		lineNumber = 1
1731	}
1732	xIndex := colNumber.ColIndex()
1733	yIndex := lineNumber.LineIndex()
1734
1735	// Go to the correct line
1736	redraw := e.GoTo(yIndex, c, status)
1737
1738	// Go to the correct column as well
1739	tabs := strings.Count(e.Line(yIndex), "\t")
1740	newScreenX := int(xIndex) + (tabs * (e.tabsSpaces.PerTab - 1))
1741	if e.pos.sx != newScreenX {
1742		redraw = true
1743	}
1744	e.pos.sx = newScreenX
1745
1746	if redraw && center {
1747		e.Center(c)
1748	}
1749	return redraw
1750}
1751
1752// Up tried to move the cursor up, and also scroll
1753func (e *Editor) Up(c *vt100.Canvas, status *StatusBar) {
1754	e.GoTo(e.DataY()-1, c, status)
1755}
1756
1757// Down tries to move the cursor down, and also scroll
1758// status is used for clearing status bar messages and can be nil
1759func (e *Editor) Down(c *vt100.Canvas, status *StatusBar) {
1760	e.GoTo(e.DataY()+1, c, status)
1761}
1762
1763// LeadingWhitespace returns the leading whitespace for this line
1764func (e *Editor) LeadingWhitespace() string {
1765	return e.CurrentLine()[:e.FirstDataPosition(e.DataY())]
1766}
1767
1768// LeadingWhitespaceAt returns the leading whitespace for a given line index
1769func (e *Editor) LeadingWhitespaceAt(y LineIndex) string {
1770	return e.Line(y)[:e.FirstDataPosition(y)]
1771}
1772
1773// LineNumber will return the current line number (data y index + 1)
1774func (e *Editor) LineNumber() LineNumber {
1775	return LineNumber(e.DataY() + 1)
1776}
1777
1778// LineIndex will return the current line index (data y index)
1779func (e *Editor) LineIndex() LineIndex {
1780	return e.DataY()
1781}
1782
1783// ColNumber will return the current column number (data x index + 1)
1784func (e *Editor) ColNumber() ColNumber {
1785	x, _ := e.DataX()
1786	return ColNumber(x + 1)
1787}
1788
1789// ColIndex will return the current column index (data x index)
1790func (e *Editor) ColIndex() ColIndex {
1791	x, _ := e.DataX()
1792	return ColIndex(x)
1793}
1794
1795// StatusMessage returns a status message, intended for being displayed at the bottom
1796func (e *Editor) StatusMessage() string {
1797	return fmt.Sprintf("line %d col %d rune %U words %d [%s]", e.LineNumber(), e.ColNumber(), e.Rune(), e.WordCount(), e.mode)
1798}
1799
1800// GoToPosition can go to the given position struct and use it as the new position
1801func (e *Editor) GoToPosition(c *vt100.Canvas, status *StatusBar, pos Position) {
1802	e.pos = pos
1803	e.redraw = e.GoTo(e.DataY(), c, status)
1804	e.redrawCursor = true
1805}
1806
1807// GoToStartOfTextLine will go to the start of the non-whitespace text, for this line
1808func (e *Editor) GoToStartOfTextLine(c *vt100.Canvas) {
1809	e.pos.SetX(c, int(e.FirstScreenPosition(e.DataY())))
1810	e.redraw = true
1811}
1812
1813// GoToNextParagraph will jump to the next line that has a blank line above it, if possible
1814// Returns true if the editor should be redrawn
1815func (e *Editor) GoToNextParagraph(c *vt100.Canvas, status *StatusBar) bool {
1816	var lastFoundBlankLine LineIndex = -1
1817	l := e.Len()
1818	for i := e.DataY() + 1; i < LineIndex(l); i++ {
1819		// Check if this is a blank line
1820		if len(strings.TrimSpace(e.Line(i))) == 0 {
1821			lastFoundBlankLine = i
1822		} else {
1823			// This is a non-blank line, check if the line above is blank (or before the first line)
1824			if lastFoundBlankLine == (i - 1) {
1825				// Yes, this is the line we wish to jump to
1826				return e.GoTo(i, c, status)
1827			}
1828		}
1829	}
1830	return false
1831}
1832
1833// GoToPrevParagraph will jump to the previous line that has a blank line below it, if possible
1834// Returns true if the editor should be redrawn
1835func (e *Editor) GoToPrevParagraph(c *vt100.Canvas, status *StatusBar) bool {
1836	var lastFoundBlankLine = LineIndex(e.Len())
1837	for i := e.DataY() - 1; i >= 0; i-- {
1838		// Check if this is a blank line
1839		if len(strings.TrimSpace(e.Line(i))) == 0 {
1840			lastFoundBlankLine = i
1841		} else {
1842			// This is a non-blank line, check if the line below is blank (or after the last line)
1843			if lastFoundBlankLine == (i + 1) {
1844				// Yes, this is the line we wish to jump to
1845				return e.GoTo(i, c, status)
1846			}
1847		}
1848	}
1849	return false
1850}
1851
1852// Center will scroll the contents so that the line with the cursor ends up in the center of the screen
1853func (e *Editor) Center(c *vt100.Canvas) {
1854	// Find the terminal height
1855	h := 25
1856	if c != nil {
1857		h = int(c.Height())
1858	}
1859
1860	// General information about how the positions and offsets relate:
1861	//
1862	// offset + screen y = data y
1863	//
1864	// offset = e.pos.offset
1865	// screen y = e.pos.sy
1866	// data y = e.DataY()
1867	//
1868	// offset = data y - screen y
1869
1870	// Plan:
1871	// 1. offset = data y - (h / 2)
1872	// 2. screen y = data y - offset
1873
1874	// Find the center line
1875	centerY := h / 2
1876	y := int(e.DataY())
1877	if y < centerY {
1878		// Not enough room to adjust
1879		return
1880	}
1881
1882	// Find the new offset and y position
1883	newOffset := y - centerY
1884	newScreenY := y - newOffset
1885
1886	// Assign the new values to the editor
1887	e.pos.offsetY = newOffset
1888	e.pos.sy = newScreenY
1889}
1890
1891// CommentOn will insert a comment marker (like # or //) in front of a line
1892func (e *Editor) CommentOn(commentMarker string) {
1893	e.SetCurrentLine(commentMarker + " " + e.CurrentLine())
1894}
1895
1896// CommentOff will remove "//" or "// " from the front of the line if "//" is given
1897func (e *Editor) CommentOff(commentMarker string) {
1898	var (
1899		changed      bool
1900		newContents  string
1901		contents     = e.CurrentLine()
1902		trimContents = strings.TrimSpace(contents)
1903	)
1904	commentMarkerPlusSpace := commentMarker + " "
1905	if strings.HasPrefix(trimContents, commentMarkerPlusSpace) {
1906		// toggle off comment
1907		newContents = strings.Replace(contents, commentMarkerPlusSpace, "", 1)
1908		changed = true
1909	} else if strings.HasPrefix(trimContents, commentMarker) {
1910		// toggle off comment
1911		newContents = strings.Replace(contents, commentMarker, "", 1)
1912		changed = true
1913	}
1914	if changed {
1915		e.SetCurrentLine(newContents)
1916		// If the line was shortened and the cursor ended up after the line, move it
1917		if e.AfterEndOfLine() {
1918			e.End(nil)
1919		}
1920	}
1921}
1922
1923// CurrentLineCommented checks if the current trimmed line starts with "//", if "//" is given
1924func (e *Editor) CurrentLineCommented(commentMarker string) bool {
1925	return strings.HasPrefix(e.TrimmedLine(), commentMarker)
1926}
1927
1928// ForEachLineInBlock will move the cursor and run the given function for
1929// each line in the current block of text (until newline or end of document)
1930// Also takes a string that will be passed on to the function.
1931func (e *Editor) ForEachLineInBlock(c *vt100.Canvas, f func(string), commentMarker string) {
1932	downCounter := 0
1933	for !e.EmptyRightTrimmedLine() {
1934		f(commentMarker)
1935		if e.AtOrAfterEndOfDocument() {
1936			break
1937		}
1938		e.Down(c, nil)
1939		downCounter++
1940		if downCounter > 100 { // safeguard
1941			break
1942		}
1943	}
1944	// Go up again
1945	for i := downCounter; i > 0; i-- {
1946		e.Up(c, nil)
1947	}
1948}
1949
1950// Block will return the text from the given line until
1951// either a newline or the end of the document.
1952func (e *Editor) Block(n LineIndex) string {
1953	var (
1954		bb, lb strings.Builder // block string builder and line string builder
1955		line   []rune
1956		ok     bool
1957		s      string
1958	)
1959	for {
1960		line, ok = e.lines[int(n)]
1961		n++
1962		if !ok || len(line) == 0 {
1963			// End of document, empty line or invalid line: end of block
1964			return bb.String()
1965		}
1966		lb.Reset()
1967		for _, r := range line {
1968			lb.WriteRune(r)
1969		}
1970		s = lb.String()
1971		if len(strings.TrimSpace(s)) == 0 {
1972			// Empty trimmed line, end of block
1973			return bb.String()
1974		}
1975		// Save this line to bb
1976		bb.WriteString(s)
1977		// And add a newline
1978		bb.Write([]byte{'\n'})
1979	}
1980}
1981
1982// ToggleCommentBlock will toggle comments until a blank line or the end of the document is reached
1983// The amount of existing commented lines is considered before deciding to comment the block in or out
1984func (e *Editor) ToggleCommentBlock(c *vt100.Canvas) {
1985	// If most of the lines in the block are comments, comment it out
1986	// If most of the lines in the block are not comments, comment it in
1987
1988	var (
1989		downCounter    = 0
1990		commentCounter = 0
1991		commentMarker  = e.SingleLineCommentMarker()
1992	)
1993
1994	// Count the commented lines in this block while going down
1995	for !e.EmptyRightTrimmedLine() {
1996		if e.CurrentLineCommented(commentMarker) {
1997			commentCounter++
1998		}
1999		if e.AtOrAfterEndOfDocument() {
2000			break
2001		}
2002		e.Down(c, nil)
2003		downCounter++
2004		if downCounter > 100 { // safeguard
2005			break
2006		}
2007	}
2008	// Go up again
2009	for i := downCounter; i > 0; i-- {
2010		e.Up(c, nil)
2011	}
2012
2013	// Check if most lines are commented out
2014	mostLinesAreComments := commentCounter >= (downCounter / 2)
2015
2016	// Handle the single-line case differently
2017	if downCounter == 1 && commentCounter == 0 {
2018		e.CommentOn(commentMarker)
2019	} else if downCounter == 1 && commentCounter == 1 {
2020		e.CommentOff(commentMarker)
2021	} else if mostLinesAreComments {
2022		e.ForEachLineInBlock(c, e.CommentOff, commentMarker)
2023	} else {
2024		e.ForEachLineInBlock(c, e.CommentOn, commentMarker)
2025	}
2026}
2027
2028// NewLine inserts a new line below and moves down one step
2029func (e *Editor) NewLine(c *vt100.Canvas, status *StatusBar) {
2030	e.InsertLineBelow()
2031	e.Down(c, status)
2032}
2033
2034// ChopLine takes a string where the tabs have been expanded
2035// and scrolls it + chops it up for display in the current viewport.
2036// e.pos.offsetX and the given viewportWidth are respected.
2037func (e *Editor) ChopLine(line string, viewportWidth int) string {
2038	var screenLine string
2039	// Shorten the screen line to account for the X offset
2040	if len([]rune(line)) > e.pos.offsetX {
2041		screenLine = line[e.pos.offsetX:]
2042	}
2043	// Shorten the screen line to account for the terminal width
2044	if len(string(screenLine)) >= viewportWidth {
2045		screenLine = screenLine[:viewportWidth]
2046	}
2047	return screenLine
2048}
2049
2050// HorizontalScrollIfNeeded will scroll along the X axis, if needed
2051func (e *Editor) HorizontalScrollIfNeeded(c *vt100.Canvas) {
2052	x := e.pos.sx
2053	w := 80
2054	if c != nil {
2055		w = int(c.W())
2056	}
2057	if x < w {
2058		e.pos.offsetX = 0
2059	} else {
2060		e.pos.offsetX = (x - w) + 1
2061		e.pos.sx -= e.pos.offsetX
2062	}
2063	e.redraw = true
2064	e.redrawCursor = true
2065}
2066
2067// VerticalScrollIfNeeded will scroll along the X axis, if needed
2068func (e *Editor) VerticalScrollIfNeeded(c *vt100.Canvas, status *StatusBar) {
2069	y := e.pos.sy
2070	h := 25
2071	if c != nil {
2072		h = int(c.H())
2073	}
2074	if y < h {
2075		e.pos.offsetY = 0
2076	} else {
2077		e.pos.offsetY = (y - h) + 1
2078		e.pos.sy -= e.pos.offsetY
2079	}
2080	e.redraw = true
2081	e.redrawCursor = true
2082}
2083
2084// InsertFile inserts the contents of a file at the current location
2085func (e *Editor) InsertFile(c *vt100.Canvas, filename string) error {
2086	data, err := ioutil.ReadFile(filename)
2087	if err != nil {
2088		return err
2089	}
2090	s := opinionatedStringReplacer.Replace(strings.TrimRightFunc(string(data), unicode.IsSpace))
2091	e.InsertStringAndMove(c, s)
2092	return nil
2093}
2094
2095// AbsFilename returns the absolute filename for this editor,
2096// cleaned with filepath.Clean.
2097func (e *Editor) AbsFilename() (string, error) {
2098	absFilename, err := filepath.Abs(e.filename)
2099	if err != nil {
2100		return "", err
2101	}
2102	return filepath.Clean(absFilename), nil
2103}
2104
2105// Switch replaces the current editor with a new Editor that opens the given file.
2106// The undo stack is also swapped.
2107// Only works for switching to one file, and then back again.
2108func (e *Editor) Switch(c *vt100.Canvas, tty *vt100.TTY, status *StatusBar, lk *LockKeeper, filenameToOpen string, forceOpen bool) error {
2109	absFilename, err := e.AbsFilename()
2110	if err != nil {
2111		return err
2112	}
2113	// Unlock and save the lock file
2114	lk.Unlock(absFilename)
2115	lk.Save()
2116	// Now open the header filename instead of the current file. Save the current file first.
2117	e.Save(c, tty)
2118	// Save the current location in the location history and write it to file
2119	e.SaveLocation(absFilename, e.locationHistory)
2120
2121	var (
2122		e2            *Editor
2123		statusMessage string
2124	)
2125
2126	if switchBuffer.Len() == 1 {
2127		// Load the Editor from the switchBuffer if switchBuffer has length 1, then use that editor.
2128		switchBuffer.Restore(e)
2129		undo, switchUndoBackup = switchUndoBackup, undo
2130	} else {
2131		e2, statusMessage, err = NewEditor(tty, c, filenameToOpen, LineNumber(0), ColNumber(0), e.Theme, e.syntaxHighlight)
2132		if err == nil { // no issue
2133			// Save the current Editor to the switchBuffer if switchBuffer if empty, then use the new editor.
2134			switchBuffer.Snapshot(e)
2135
2136			// Now use e2 as the current editor
2137			*e = *e2
2138			(*e).lines = (*e2).lines
2139			(*e).pos = (*e2).pos
2140
2141		} else {
2142			panic(err)
2143		}
2144		undo, switchUndoBackup = switchUndoBackup, undo
2145	}
2146
2147	e.redraw = true
2148	e.redrawCursor = true
2149
2150	if statusMessage != "" {
2151		status.Clear(c)
2152		status.SetMessage(statusMessage)
2153		status.Show(c, e)
2154	}
2155
2156	return err
2157}
2158
2159// TrimmedLine returns the current line, trimmed in both ends
2160func (e *Editor) TrimmedLine() string {
2161	return strings.TrimSpace(e.CurrentLine())
2162}
2163
2164// LineContentsFromCursorPosition returns the rest of the line,
2165// from the current cursor position, trimmed.
2166func (e *Editor) LineContentsFromCursorPosition() string {
2167	x, err := e.DataX()
2168	if err != nil {
2169		return ""
2170	}
2171	return strings.TrimSpace(e.CurrentLine()[x:])
2172}
2173
2174// LettersBeforeCursor returns the current word up until the cursor (for autocompletion)
2175func (e *Editor) LettersBeforeCursor() string {
2176	y := int(e.DataY())
2177	runes, ok := e.lines[y]
2178	if !ok {
2179		// This should never happen
2180		return ""
2181	}
2182	// Either find x or use the last index of the line
2183	x, err := e.DataX()
2184	if err != nil {
2185		x = len(runes)
2186	}
2187
2188	var word []rune
2189
2190	// Loop from the position before the current one and then leftwards on the current line
2191	for i := x - 1; i >= 0; i-- {
2192		r := runes[i]
2193		if !unicode.IsLetter(r) {
2194			break
2195		}
2196		// Gather the letters in reverse
2197		word = append([]rune{r}, word...)
2198	}
2199	return string(word)
2200}
2201
2202// LettersOrDotBeforeCursor returns the current word up until the cursor (for autocompletion).
2203// Will also include ".".
2204func (e *Editor) LettersOrDotBeforeCursor() string {
2205	y := int(e.DataY())
2206	runes, ok := e.lines[y]
2207	if !ok {
2208		// This should never happen
2209		return ""
2210	}
2211	// Either find x or use the last index of the line
2212	x, err := e.DataX()
2213	if err != nil {
2214		x = len(runes)
2215	}
2216
2217	var word []rune
2218
2219	// Loop from the position before the current one and then leftwards on the current line
2220	for i := x - 1; i >= 0; i-- {
2221		r := runes[i]
2222		if !(r == '.' || unicode.IsLetter(r)) {
2223			break
2224		}
2225		// Gather the letters in reverse
2226		word = append([]rune{r}, word...)
2227	}
2228	return string(word)
2229}
2230
2231// LastLineNumber returns the last line number (not line index) of the current file
2232func (e *Editor) LastLineNumber() LineNumber {
2233	// The last line (by line number, not by index, e.Len() returns an index which is why there is no -1)
2234	return LineNumber(e.Len())
2235}
2236