package nucular

import (
	"image"
	"image/color"
	"math"
	"runtime"
	"time"
	"unicode"

	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/mouse"

	"github.com/aarzilli/nucular/clipboard"
	"github.com/aarzilli/nucular/command"
	"github.com/aarzilli/nucular/font"
	"github.com/aarzilli/nucular/label"
	"github.com/aarzilli/nucular/rect"
	nstyle "github.com/aarzilli/nucular/style"
)

///////////////////////////////////////////////////////////////////////////////////
// TEXT EDITOR
///////////////////////////////////////////////////////////////////////////////////

type propertyStatus int

const (
	propertyDefault = propertyStatus(iota)
	propertyEdit
	propertyDrag
)

// TextEditor stores the state of a text editor.
// To add a text editor to a window create a TextEditor object with
// &TextEditor{}, store it somewhere then in the update function call
// the Edit method passing the window to it.
type TextEditor struct {
	win            *Window
	propertyStatus propertyStatus
	Cursor         int
	Buffer         []rune
	Filter         FilterFunc
	Flags          EditFlags
	CursorFollow   bool
	Redraw         bool

	Maxlen int

	PasswordChar rune // if non-zero all characters are displayed like this character

	Initialized            bool
	Active                 bool
	InsertMode             bool
	Scrollbar              image.Point
	SelectStart, SelectEnd int
	HasPreferredX          bool
	SingleLine             bool
	PreferredX             int
	Undo                   textUndoState

	drawchunks []drawchunk

	lastClickCoord  image.Point
	lastClickTime   time.Time
	clickCount      int
	trueSelectStart int

	needle []rune

	password []rune // support buffer for drawing PasswordChar!=0 fields
}

type drawchunk struct {
	rect.Rect
	start, end int
}

func (ed *TextEditor) init(win *Window) {
	if ed.Filter == nil {
		ed.Filter = FilterDefault
	}
	if !ed.Initialized {
		if ed.Flags&EditMultiline != 0 {
			ed.clearState(TextEditMultiLine)
		} else {
			ed.clearState(TextEditSingleLine)
		}

	}
	if ed.win == nil || ed.win != win {
		if ed.win == nil {
			if ed.Buffer == nil {
				ed.Buffer = []rune{}
			}
			ed.Filter = nil
			ed.Cursor = 0
		}
		ed.Redraw = true
		ed.win = win
	}
}

type EditFlags int

const (
	EditDefault  EditFlags = 0
	EditReadOnly EditFlags = 1 << iota
	EditAutoSelect
	EditSigEnter
	EditNoCursor
	EditSelectable
	EditClipboard
	EditCtrlEnterNewline
	EditNoHorizontalScroll
	EditAlwaysInsertMode
	EditMultiline
	EditNeverInsertMode
	EditFocusFollowsMouse
	EditNoContextMenu
	EditIbeamCursor

	EditSimple = EditAlwaysInsertMode
	EditField  = EditSelectable | EditClipboard | EditSigEnter
	EditBox    = EditSelectable | EditMultiline | EditClipboard
)

type EditEvents int

const (
	EditActive EditEvents = 1 << iota
	EditInactive
	EditActivated
	EditDeactivated
	EditCommitted
)

type TextEditType int

const (
	TextEditSingleLine TextEditType = iota
	TextEditMultiLine
)

type textUndoRecord struct {
	Where        int
	InsertLength int
	DeleteLength int
	Text         []rune
}

const _TEXTEDIT_UNDOSTATECOUNT = 99

type textUndoState struct {
	UndoRec   [_TEXTEDIT_UNDOSTATECOUNT]textUndoRecord
	UndoPoint int16
	RedoPoint int16
}

func strInsertText(str []rune, pos int, runes []rune) []rune {
	if cap(str) < len(str)+len(runes) {
		newcap := (cap(str) + 1) * 2
		if newcap < len(str)+len(runes) {
			newcap = len(str) + len(runes)
		}
		newstr := make([]rune, len(str), newcap)
		copy(newstr, str)
		str = newstr
	}
	str = str[:len(str)+len(runes)]
	copy(str[pos+len(runes):], str[pos:])
	copy(str[pos:], runes)
	return str
}

func strDeleteText(s []rune, pos int, dlen int) []rune {
	copy(s[pos:], s[pos+dlen:])
	s = s[:len(s)-dlen]
	return s
}

func (s *TextEditor) hasSelection() bool {
	return s.SelectStart != s.SelectEnd
}

func (edit *TextEditor) locateCoord(p image.Point, font font.Face, row_height int) int {
	x, y := p.X, p.Y

	var drawchunk *drawchunk

	for i := range edit.drawchunks {
		min := edit.drawchunks[i].Min()
		max := edit.drawchunks[i].Max()
		getprev := false
		if min.Y <= y && y <= max.Y {
			if min.X <= x && x <= max.X {
				drawchunk = &edit.drawchunks[i]
			}
			if min.X > x {
				getprev = true
			}
		} else if min.Y > y {
			getprev = true
		}
		if getprev {
			if i == 0 {
				drawchunk = &edit.drawchunks[0]
			} else {
				drawchunk = &edit.drawchunks[i-1]
			}
			break
		}
	}

	if drawchunk == nil {
		return len(edit.Buffer)
	}

	curx := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		curx += FontWidth(font, string(edit.Buffer[i:i+1]))
		if curx > x {
			return i
		}
	}

	if drawchunk.end >= len(edit.Buffer) {
		return len(edit.Buffer)
	}

	return drawchunk.end
}

func (edit *TextEditor) indexToCoord(index int, font font.Face, row_height int) image.Point {
	var drawchunk *drawchunk

	for i := range edit.drawchunks {
		if edit.drawchunks[i].start > index {
			if i == 0 {
				drawchunk = &edit.drawchunks[0]
			} else {
				drawchunk = &edit.drawchunks[i-1]
			}
			break
		}
	}

	if drawchunk == nil {
		if len(edit.drawchunks) == 0 {
			return image.Point{}
		}
		drawchunk = &edit.drawchunks[len(edit.drawchunks)-1]
	}

	x := drawchunk.X
	for i := drawchunk.start; i < drawchunk.end && i < len(edit.Buffer); i++ {
		if i >= index {
			break
		}
		x += FontWidth(font, string(edit.Buffer[i:i+1]))
	}
	if index >= len(edit.Buffer) && len(edit.Buffer) > 0 && edit.Buffer[len(edit.Buffer)-1] == '\n' {
		return image.Point{x, drawchunk.Y + drawchunk.H + drawchunk.H/2}
	}
	return image.Point{x, drawchunk.Y + drawchunk.H/2}
}

func (state *TextEditor) doubleClick(coord image.Point) bool {
	abs := func(x int) int {
		if x < 0 {
			return -x
		}
		return x
	}
	r := time.Since(state.lastClickTime) < 200*time.Millisecond && abs(state.lastClickCoord.X-coord.X) < 5 && abs(state.lastClickCoord.Y-coord.Y) < 5
	state.lastClickCoord = coord
	state.lastClickTime = time.Now()
	return r

}

func (state *TextEditor) click(coord image.Point, font font.Face, row_height int) {
	/* API click: on mouse down, move the cursor to the clicked location,
	 * and reset the selection */
	state.Cursor = state.locateCoord(coord, font, row_height)

	state.SelectStart = state.Cursor
	state.trueSelectStart = state.Cursor
	state.SelectEnd = state.Cursor
	state.HasPreferredX = false

	switch state.clickCount {
	case 2:
		state.selectWord(state.SelectEnd)
	case 3:
		state.selectLine(state.SelectEnd)
	}
}

func (state *TextEditor) drag(coord image.Point, font font.Face, row_height int) {
	/* API drag: on mouse drag, move the cursor and selection endpoint
	 * to the clicked location */
	var p int = state.locateCoord(coord, font, row_height)
	if state.SelectStart == state.SelectEnd {
		state.SelectStart = state.Cursor
		state.trueSelectStart = p
	}
	state.SelectEnd = p
	state.Cursor = state.SelectEnd

	switch state.clickCount {
	case 2:
		state.selectWord(p)
	case 3:
		state.selectLine(p)
	}
}

func (state *TextEditor) selectWord(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.towd(state.SelectStart, -1, false)
	state.SelectEnd = state.towd(state.SelectEnd, +1, true)
}

func (state *TextEditor) selectLine(end int) {
	state.SelectStart = state.trueSelectStart
	state.SelectEnd = end
	state.sortselection()
	state.SelectStart = state.tonl(state.SelectStart-1, -1)
	state.SelectEnd = state.tonl(state.SelectEnd, +1)
}

func (state *TextEditor) clamp() {
	/* make the selection/cursor state valid if client altered the string */
	if state.hasSelection() {
		if state.SelectStart > len(state.Buffer) {
			state.SelectStart = len(state.Buffer)
		}
		if state.SelectEnd > len(state.Buffer) {
			state.SelectEnd = len(state.Buffer)
		}

		/* if clamping forced them to be equal, move the cursor to match */
		if state.SelectStart == state.SelectEnd {
			state.Cursor = state.SelectStart
		}
	}

	if state.Cursor > len(state.Buffer) {
		state.Cursor = len(state.Buffer)
	}
}

// Deletes a chunk of text in the editor.
func (edit *TextEditor) Delete(where int, len int) {
	/* delete characters while updating undo */
	edit.makeundoDelete(where, len)

	edit.Buffer = strDeleteText(edit.Buffer, where, len)
	edit.HasPreferredX = false
}

// Deletes selection.
func (edit *TextEditor) DeleteSelection() {
	/* delete the section */
	edit.clamp()

	if edit.hasSelection() {
		if edit.SelectStart < edit.SelectEnd {
			edit.Delete(edit.SelectStart, edit.SelectEnd-edit.SelectStart)
			edit.Cursor = edit.SelectStart
			edit.SelectEnd = edit.Cursor
		} else {
			edit.Delete(edit.SelectEnd, edit.SelectStart-edit.SelectEnd)
			edit.Cursor = edit.SelectEnd
			edit.SelectStart = edit.Cursor
		}

		edit.HasPreferredX = false
	}
}

func (state *TextEditor) sortselection() {
	/* canonicalize the selection so start <= end */
	if state.SelectEnd < state.SelectStart {
		var temp int = state.SelectEnd
		state.SelectEnd = state.SelectStart
		state.SelectStart = temp
	}
}

func (state *TextEditor) moveToFirst() {
	/* move cursor to first character of selection */
	if state.hasSelection() {
		state.sortselection()
		state.Cursor = state.SelectStart
		state.SelectEnd = state.SelectStart
		state.HasPreferredX = false
	}
}

func (state *TextEditor) moveToLast() {
	/* move cursor to last character of selection */
	if state.hasSelection() {
		state.sortselection()
		state.clamp()
		state.Cursor = state.SelectEnd
		state.SelectStart = state.SelectEnd
		state.HasPreferredX = false
	}
}

// Moves to the beginning or end of a line
func (state *TextEditor) tonl(start int, dir int) int {
	sz := len(state.Buffer)

	i := start
	if i < 0 {
		return 0
	}
	if i >= sz {
		i = sz - 1
	}
	for ; (i >= 0) && (i < sz); i += dir {
		c := state.Buffer[i]

		if c == '\n' {
			if dir >= 0 {
				return i
			} else {
				return i + 1
			}
		}
	}
	if dir < 0 {
		return 0
	} else {
		return sz
	}
}

// Moves to the beginning or end of an alphanumerically delimited word
func (state *TextEditor) towd(start int, dir int, dontForceAdvance bool) int {
	first := (dir < 0)
	notfirst := !first
	var i int
	for i = start; (i >= 0) && (i < len(state.Buffer)); i += dir {
		c := state.Buffer[i]
		if !(unicode.IsLetter(c) || unicode.IsDigit(c) || (c == '_')) {
			if !first && !dontForceAdvance {
				i++
			}
			break
		}
		first = notfirst
	}
	if i < 0 {
		i = 0
	}
	return i
}

func (state *TextEditor) prepSelectionAtCursor() {
	/* update selection and cursor to match each other */
	if !state.hasSelection() {
		state.SelectEnd = state.Cursor
		state.SelectStart = state.SelectEnd
	} else {
		state.Cursor = state.SelectEnd
	}
}

func (edit *TextEditor) Cut() int {
	if edit.Flags&EditReadOnly != 0 {
		return 0
	}
	/* API cut: delete selection */
	if edit.hasSelection() {
		edit.DeleteSelection() /* implicitly clamps */
		edit.HasPreferredX = false
		return 1
	}

	return 0
}

// Paste from clipboard
func (edit *TextEditor) Paste(ctext string) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	/* if there's a selection, the paste should delete it */
	edit.clamp()

	edit.DeleteSelection()

	text := []rune(ctext)

	edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text)

	edit.makeundoInsert(edit.Cursor, len(text))
	edit.Cursor += len(text)
	edit.HasPreferredX = false
}

func (edit *TextEditor) Text(text []rune) {
	if edit.Flags&EditReadOnly != 0 {
		return
	}

	for i := range text {
		/* can't add newline in single-line mode */
		if text[i] == '\n' && edit.SingleLine {
			break
		}

		/* can't add tab in single-line mode */
		if text[i] == '\t' && edit.SingleLine {
			break
		}

		/* filter incoming text */
		if edit.Filter != nil && !edit.Filter(text[i]) {
			continue
		}

		if edit.InsertMode && !edit.hasSelection() && edit.Cursor < len(edit.Buffer) {
			edit.makeundoReplace(edit.Cursor, 1, 1)
			edit.Buffer = strDeleteText(edit.Buffer, edit.Cursor, 1)
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.Cursor++
			edit.HasPreferredX = false
		} else {
			edit.DeleteSelection() /* implicitly clamps */
			edit.Buffer = strInsertText(edit.Buffer, edit.Cursor, text[i:i+1])
			edit.makeundoInsert(edit.Cursor, 1)
			edit.Cursor++
			edit.HasPreferredX = false
		}
	}
}

func (state *TextEditor) key(e key.Event, font font.Face, row_height int, area_height int) {
	readOnly := state.Flags&EditReadOnly != 0
retry:
	switch e.Code {
	case key.CodeZ:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.DoRedo()
				state.HasPreferredX = false

			} else {
				state.DoUndo()
				state.HasPreferredX = false
			}
		}

	case key.CodeK:
		if readOnly {
			return
		}
		if e.Modifiers&key.ModControl != 0 {
			state.trueSelectStart = state.Cursor
			state.selectLine(state.Cursor)
			state.DeleteSelection()
		}

	case key.CodeInsert:
		state.InsertMode = !state.InsertMode

	case key.CodeLeftArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor-1, -1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToFirst()
				} else {
					state.Cursor = state.towd(state.Cursor-1, -1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()

				/* move selection left */
				if state.SelectEnd > 0 {
					state.SelectEnd--
				}
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				/* if currently there's a selection,
				 * move cursor to start of selection */
				if state.hasSelection() {
					state.moveToFirst()
				} else if state.Cursor > 0 {
					state.Cursor--
				}
				state.HasPreferredX = false
			}
		}

	case key.CodeRightArrow:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				if !state.hasSelection() {
					state.prepSelectionAtCursor()
				}
				state.Cursor = state.towd(state.Cursor, +1, false)
				state.SelectEnd = state.Cursor
				state.clamp()
			} else {
				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor = state.towd(state.Cursor, +1, false)
					state.clamp()
				}
			}
		} else {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()

				/* move selection right */
				state.SelectEnd++

				state.clamp()
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				/* if currently there's a selection,
				 * move cursor to end of selection */
				if state.hasSelection() {
					state.moveToLast()
				} else {
					state.Cursor++
				}
				state.clamp()
				state.HasPreferredX = false
			}
		}
	case key.CodeDownArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}
		state.verticalCursorMove(e, font, row_height, +row_height)

	case key.CodeUpArrow:
		if state.SingleLine {
			e.Code = key.CodeRightArrow
			goto retry
		}
		state.verticalCursorMove(e, font, row_height, -row_height)

	case key.CodePageDown:
		if state.SingleLine {
			break
		}
		state.verticalCursorMove(e, font, row_height, +area_height/2)

	case key.CodePageUp:
		if state.SingleLine {
			break
		}
		state.verticalCursorMove(e, font, row_height, -area_height/2)

	case key.CodeDeleteForward:
		if readOnly {
			return
		}
		if state.hasSelection() {
			state.DeleteSelection()
		} else {
			if state.Cursor < len(state.Buffer) {
				state.Delete(state.Cursor, 1)
			}
		}

		state.HasPreferredX = false

	case key.CodeDeleteBackspace:
		if readOnly {
			return
		}
		switch {
		case state.hasSelection():
			state.DeleteSelection()
		case e.Modifiers&key.ModControl != 0:
			state.SelectEnd = state.Cursor
			state.SelectStart = state.towd(state.Cursor-1, -1, false)
			state.DeleteSelection()
		default:
			state.clamp()
			if state.Cursor > 0 {
				state.Delete(state.Cursor-1, 1)
				state.Cursor--
			}
		}
		state.HasPreferredX = false

	case key.CodeHome:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = 0
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.Cursor = state.SelectStart
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			start := state.tonl(state.Cursor-1, -1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.SelectEnd = start
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.clamp()
				state.moveToFirst()
				state.Cursor = start
				state.HasPreferredX = false
			}
		}

	case key.CodeA:
		if e.Modifiers&key.ModControl != 0 {
			state.clamp()
			state.moveToFirst()
			state.Cursor = state.tonl(state.Cursor-1, -1)
			state.HasPreferredX = false
		}

	case key.CodeEnd:
		if e.Modifiers&key.ModControl != 0 {
			if e.Modifiers&key.ModShift != 0 {
				state.prepSelectionAtCursor()
				state.SelectEnd = len(state.Buffer)
				state.Cursor = state.SelectEnd
				state.HasPreferredX = false
			} else {
				state.Cursor = len(state.Buffer)
				state.SelectEnd = 0
				state.SelectStart = state.SelectEnd
				state.HasPreferredX = false
			}
		} else {
			state.clamp()
			end := state.tonl(state.Cursor, +1)
			if e.Modifiers&key.ModShift != 0 {
				state.clamp()
				state.prepSelectionAtCursor()
				state.HasPreferredX = false
				state.Cursor = end
				state.SelectEnd = state.Cursor
			} else {
				state.clamp()
				state.moveToFirst()
				state.HasPreferredX = false
				state.Cursor = end
			}
		}

	case key.CodeE:
		if e.Modifiers&key.ModControl != 0 {
			end := state.tonl(state.Cursor, +1)
			state.clamp()
			state.moveToFirst()
			state.HasPreferredX = false
			state.Cursor = end
		}
	}
}

func (state *TextEditor) verticalCursorMove(e key.Event, font font.Face, row_height int, offset int) {
	if e.Modifiers&key.ModShift != 0 {
		state.prepSelectionAtCursor()
	} else if state.hasSelection() {
		if offset < 0 {
			state.moveToFirst()
		} else {
			state.moveToLast()
		}
	}

	state.clamp()

	p := state.indexToCoord(state.Cursor, font, row_height)
	p.Y += offset

	if state.HasPreferredX {
		p.X = state.PreferredX
	} else {
		state.HasPreferredX = true
		state.PreferredX = p.X
	}
	state.Cursor = state.locateCoord(p, font, row_height)

	state.clamp()

	if e.Modifiers&key.ModShift != 0 {
		state.SelectEnd = state.Cursor
	}
}

func texteditFlushRedo(state *textUndoState) {
	state.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
}

func texteditDiscardUndo(state *textUndoState) {
	/* discard the oldest entry in the undo list */
	if state.UndoPoint > 0 {
		state.UndoPoint--
		copy(state.UndoRec[:], state.UndoRec[1:])
	}
}

func texteditCreateUndoRecord(state *textUndoState, numchars int) *textUndoRecord {
	/* any time we create a new undo record, we discard redo*/
	texteditFlushRedo(state)

	/* if we have no free records, we have to make room,
	 * by sliding the existing records down */
	if int(state.UndoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		texteditDiscardUndo(state)
	}

	r := &state.UndoRec[state.UndoPoint]
	state.UndoPoint++
	return r
}

func texteditCreateundo(state *textUndoState, pos int, insert_len int, delete_len int) *textUndoRecord {
	r := texteditCreateUndoRecord(state, insert_len)

	r.Where = pos
	r.InsertLength = insert_len
	r.DeleteLength = delete_len
	r.Text = nil

	return r
}

func (edit *TextEditor) DoUndo() {
	var s *textUndoState = &edit.Undo
	var u textUndoRecord
	var r *textUndoRecord
	if s.UndoPoint == 0 {
		return
	}

	/* we need to do two things: apply the undo record, and create a redo record */
	u = s.UndoRec[s.UndoPoint-1]

	r = &s.UndoRec[s.RedoPoint-1]
	r.Text = nil

	r.InsertLength = u.DeleteLength
	r.DeleteLength = u.InsertLength
	r.Where = u.Where

	if u.DeleteLength != 0 {
		r.Text = make([]rune, u.DeleteLength)
		copy(r.Text, edit.Buffer[u.Where:u.Where+u.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, u.Where, u.DeleteLength)
	}

	/* check type of recorded action: */
	if u.InsertLength != 0 {
		/* easy case: was a deletion, so we need to insert n characters */
		edit.Buffer = strInsertText(edit.Buffer, u.Where, u.Text)
	}

	edit.Cursor = u.Where + u.InsertLength

	s.UndoPoint--
	s.RedoPoint--
}

func (edit *TextEditor) DoRedo() {
	var s *textUndoState = &edit.Undo
	var u *textUndoRecord
	var r textUndoRecord
	if int(s.RedoPoint) == _TEXTEDIT_UNDOSTATECOUNT {
		return
	}

	/* we need to do two things: apply the redo record, and create an undo record */
	u = &s.UndoRec[s.UndoPoint]

	r = s.UndoRec[s.RedoPoint]

	/* we KNOW there must be room for the undo record, because the redo record
	was derived from an undo record */
	u.DeleteLength = r.InsertLength

	u.InsertLength = r.DeleteLength
	u.Where = r.Where
	u.Text = nil

	if r.DeleteLength != 0 {
		u.Text = make([]rune, r.DeleteLength)
		copy(u.Text, edit.Buffer[r.Where:r.Where+r.DeleteLength])
		edit.Buffer = strDeleteText(edit.Buffer, r.Where, r.DeleteLength)
	}

	if r.InsertLength != 0 {
		/* easy case: need to insert n characters */
		edit.Buffer = strInsertText(edit.Buffer, r.Where, r.Text)
	}

	edit.Cursor = r.Where + r.InsertLength

	s.UndoPoint++
	s.RedoPoint++
}

func (state *TextEditor) makeundoInsert(where int, length int) {
	texteditCreateundo(&state.Undo, where, 0, length)
}

func (state *TextEditor) makeundoDelete(where int, length int) {
	u := texteditCreateundo(&state.Undo, where, length, 0)
	u.Text = make([]rune, length)
	copy(u.Text, state.Buffer[where:where+length])
}

func (state *TextEditor) makeundoReplace(where int, old_length int, new_length int) {
	u := texteditCreateundo(&state.Undo, where, old_length, new_length)
	u.Text = make([]rune, old_length)
	copy(u.Text, state.Buffer[where:where+old_length])
}

func (state *TextEditor) clearState(type_ TextEditType) {
	/* reset the state to default */
	state.Undo.UndoPoint = 0

	state.Undo.RedoPoint = int16(_TEXTEDIT_UNDOSTATECOUNT)
	state.HasPreferredX = false
	state.PreferredX = 0
	//state.CursorAtEndOfLine = 0
	state.Initialized = true
	state.SingleLine = type_ == TextEditSingleLine
	state.InsertMode = false
}

func (edit *TextEditor) SelectAll() {
	edit.SelectStart = 0
	edit.SelectEnd = len(edit.Buffer)
}

func (edit *TextEditor) editDrawText(out *command.Buffer, style *nstyle.Edit, pos image.Point, x_margin int, text []rune, textOffset int, row_height int, f font.Face, background color.RGBA, foreground color.RGBA, is_selected bool) (posOut image.Point) {
	if len(text) == 0 {
		return pos
	}
	var line_offset int = 0
	var line_count int = 0
	var txt textWidget
	txt.Background = background
	txt.Text = foreground

	pos_x, pos_y := pos.X, pos.Y
	start := 0

	tabsz := glyphAdvance(f, ' ') * tabSizeInSpaces
	pwsz := glyphAdvance(f, '*')

	measureText := func(start, end int) int {
		if edit.PasswordChar != 0 {
			return pwsz * (end - start)
		}
		// XXX calculating text width here is slow figure out why
		return measureRunes(f, text[start:end])
	}

	getText := func(start, end int) string {
		if edit.PasswordChar != 0 {
			n := end - start
			if n >= len(edit.password) {
				edit.password = make([]rune, n)
				for i := range edit.password {
					edit.password[i] = edit.PasswordChar
				}
			}
			return string(edit.password[:n])
		}
		return string(text[start:end])
	}

	flushLine := func(index int) rect.Rect {
		// new line sepeator so draw previous line
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = nk_null_rect.W
		lblrect.X = pos_x

		if is_selected { // selection needs to draw different background color
			if index == len(text) || (index == start && start == 0) {
				lblrect.W = measureText(start, index)
			}
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x = x_margin

		return lblrect
	}

	flushTab := func(index int) rect.Rect {
		var lblrect rect.Rect
		lblrect.Y = pos_y + line_offset
		lblrect.H = row_height
		lblrect.W = measureText(start, index)
		lblrect.X = pos_x

		lblrect.W = int(math.Floor(float64(lblrect.X+lblrect.W-x_margin)/float64(tabsz))+1)*tabsz + x_margin - lblrect.X

		if is_selected {
			out.FillRect(lblrect, 0, background)
		}
		edit.drawchunks = append(edit.drawchunks, drawchunk{lblrect, start + textOffset, index + textOffset})
		widgetText(out, lblrect, getText(start, index), &txt, "LC", f)

		pos_x += lblrect.W

		return lblrect
	}

	for index, glyph := range text {
		switch glyph {
		case '\t':
			flushTab(index)
			start = index + 1
		case '\n':
			flushLine(index)
			line_count++
			start = index + 1
			line_offset += row_height

		case '\r':
			// do nothing
		}
	}

	if start >= len(text) {
		return image.Point{pos_x, pos_y + line_offset}
	}

	// draw last line
	lblrect := flushLine(len(text))
	lblrect.W = measureText(start, len(text))

	return image.Point{lblrect.X + lblrect.W, lblrect.Y}
}

func (ed *TextEditor) doEdit(bounds rect.Rect, style *nstyle.Edit, inp *Input, cut, copy, paste bool) (ret EditEvents) {
	font := ed.win.ctx.Style.Font
	state := ed.win.widgets.PrevState(bounds)

	ed.clamp()

	// visible text area calculation
	var area rect.Rect
	area.X = bounds.X + style.Padding.X + style.Border
	area.Y = bounds.Y + style.Padding.Y + style.Border
	area.W = bounds.W - (2.0*style.Padding.X + 2*style.Border)
	area.H = bounds.H - (2.0*style.Padding.Y + 2*style.Border)
	if ed.Flags&EditMultiline != 0 {
		area.H = area.H - style.ScrollbarSize.Y
	}
	var row_height int
	if ed.Flags&EditMultiline != 0 {
		row_height = FontHeight(font) + style.RowPadding
	} else {
		row_height = area.H
	}

	/* update edit state */
	prev_state := ed.Active

	if ed.win.ctx.activateEditor != nil {
		if ed.win.ctx.activateEditor == ed {
			ed.Active = true
			if ed.win.flags&windowDocked != 0 {
				ed.win.ctx.dockedWindowFocus = ed.win.idx
			}
		} else {
			ed.Active = false
		}
	}

	is_hovered := inp.Mouse.HoveringRect(bounds)

	if ed.Flags&EditFocusFollowsMouse != 0 {
		if inp != nil {
			ed.Active = is_hovered
		}
	} else {
		if inp != nil && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked && inp.Mouse.Buttons[mouse.ButtonLeft].Down {
			ed.Active = inp.Mouse.HoveringRect(bounds)
		}
	}

	/* (de)activate text editor */
	var select_all bool
	if !prev_state && ed.Active {
		type_ := TextEditSingleLine
		if ed.Flags&EditMultiline != 0 {
			type_ = TextEditMultiLine
		}
		ed.clearState(type_)
		if ed.Flags&EditAlwaysInsertMode != 0 {
			ed.InsertMode = true
		}
		if ed.Flags&EditAutoSelect != 0 {
			select_all = true
		}
	} else if !ed.Active {
		ed.InsertMode = false
	}

	if ed.Flags&EditNeverInsertMode != 0 {
		ed.InsertMode = false
	}

	if ed.Active {
		ret = EditActive
	} else {
		ret = EditInactive
	}
	if prev_state != ed.Active {
		if ed.Active {
			ret |= EditActivated
		} else {
			ret |= EditDeactivated
		}
	}

	/* handle user input */
	cursor_follow := ed.CursorFollow
	ed.CursorFollow = false
	if ed.Active && inp != nil {
		inpos := inp.Mouse.Pos
		indelta := inp.Mouse.Delta
		coord := image.Point{(inpos.X - area.X), (inpos.Y - area.Y)}

		var isHovered bool
		{
			areaWithoutScrollbar := area
			areaWithoutScrollbar.W -= style.ScrollbarSize.X
			isHovered = inp.Mouse.HoveringRect(areaWithoutScrollbar)
		}

		var autoscrollTop bool
		{
			a := area
			a.W -= style.ScrollbarSize.X
			a.H = FontHeight(font) / 2
			autoscrollTop = inp.Mouse.HoveringRect(a) && inp.Mouse.Buttons[mouse.ButtonLeft].Down
		}

		var autoscrollBot bool
		{
			a := area
			a.W -= style.ScrollbarSize.X
			a.Y = a.Y + a.H - FontHeight(font)/2
			a.H = FontHeight(font) / 2
			autoscrollBot = inp.Mouse.HoveringRect(a) && inp.Mouse.Buttons[mouse.ButtonLeft].Down
		}

		/* mouse click handler */
		if select_all {
			ed.SelectAll()
		} else if isHovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && inp.Mouse.Buttons[mouse.ButtonLeft].Clicked {
			if ed.doubleClick(coord) {
				ed.clickCount++
				if ed.clickCount > 3 {
					ed.clickCount = 3
				}
			} else {
				ed.clickCount = 1
			}
			ed.click(coord, font, row_height)
		} else if isHovered && inp.Mouse.Buttons[mouse.ButtonLeft].Down && (indelta.X != 0.0 || indelta.Y != 0.0) {
			ed.drag(coord, font, row_height)
			cursor_follow = true
		} else if autoscrollTop {
			coord1 := coord
			coord1.Y -= FontHeight(font)
			ed.drag(coord1, font, row_height)
			cursor_follow = true
		} else if autoscrollBot {
			coord1 := coord
			coord1.Y += FontHeight(font)
			ed.drag(coord1, font, row_height)
			cursor_follow = true
		}

		/* text input */
		if inp.Keyboard.Text != "" {
			ed.Text([]rune(inp.Keyboard.Text))
			cursor_follow = true
		}

		clipboardModifier := key.ModControl
		if runtime.GOOS == "darwin" {
			clipboardModifier = key.ModMeta
		}

		for _, e := range inp.Keyboard.Keys {
			switch e.Code {
			case key.CodeReturnEnter:
				if ed.Flags&EditCtrlEnterNewline != 0 && e.Modifiers&key.ModShift != 0 {
					ed.Text([]rune{'\n'})
					cursor_follow = true
				} else if ed.Flags&EditSigEnter != 0 {
					ret = EditInactive
					ret |= EditDeactivated
					if ed.Flags&EditReadOnly == 0 {
						ret |= EditCommitted
					}
					ed.Active = false
				}

			case key.CodeX:
				if e.Modifiers&clipboardModifier != 0 {
					cut = true
				}

			case key.CodeC:
				if e.Modifiers&clipboardModifier != 0 {
					copy = true
				}

			case key.CodeV:
				if e.Modifiers&clipboardModifier != 0 {
					paste = true
				}

			case key.CodeF:
				if e.Modifiers&clipboardModifier != 0 {
					ed.popupFind()
				}

			case key.CodeG:
				if e.Modifiers&clipboardModifier != 0 {
					ed.lookForward(true)
					cursor_follow = true
				}

			default:
				ed.key(e, font, row_height, area.H)
				cursor_follow = true
			}

		}

		/* cut & copy handler */
		if (copy || cut) && (ed.Flags&EditClipboard != 0) {
			var begin, end int
			if ed.SelectStart > ed.SelectEnd {
				begin = ed.SelectEnd
				end = ed.SelectStart
			} else {
				begin = ed.SelectStart
				end = ed.SelectEnd
			}
			clipboard.Set(string(ed.Buffer[begin:end]))
			if cut {
				ed.Cut()
				cursor_follow = true
			}
		}

		/* paste handler */
		if paste && (ed.Flags&EditClipboard != 0) {
			ed.Paste(clipboard.Get())
			cursor_follow = true
		}

	}

	/* set widget state */
	if ed.Active {
		state = nstyle.WidgetStateActive
	} else {
		state = nstyle.WidgetStateInactive
	}
	if is_hovered {
		state |= nstyle.WidgetStateHovered
	}

	var d drawableTextEditor

	/* text pointer positions */
	var selection_begin, selection_end int
	if ed.SelectStart < ed.SelectEnd {
		selection_begin = ed.SelectStart
		selection_end = ed.SelectEnd
	} else {
		selection_begin = ed.SelectEnd
		selection_end = ed.SelectStart
	}

	d.SelectionBegin, d.SelectionEnd = selection_begin, selection_end

	d.Edit = ed
	d.State = state
	d.Style = style
	d.Scaling = ed.win.ctx.Style.Scaling
	d.Bounds = bounds
	d.Area = area
	d.RowHeight = row_height
	d.hasInput = inp.Mouse.valid
	ed.win.widgets.Add(state, bounds)
	d.Draw(&ed.win.ctx.Style, &ed.win.cmds)

	/* scrollbar */
	if cursor_follow {
		cursor_pos := d.CursorPos
		/* update scrollbar to follow cursor */
		if ed.Flags&EditNoHorizontalScroll == 0 {
			/* horizontal scroll */
			scroll_increment := area.W / 2
			if (cursor_pos.X < ed.Scrollbar.X) || ((ed.Scrollbar.X+area.W)-cursor_pos.X < FontWidth(font, "i")) {
				ed.Scrollbar.X = max(0, cursor_pos.X-scroll_increment)
			}
		} else {
			ed.Scrollbar.X = 0
		}

		if ed.Flags&EditMultiline != 0 {
			/* vertical scroll */
			if cursor_pos.Y < ed.Scrollbar.Y {
				ed.Scrollbar.Y = max(0, cursor_pos.Y-row_height)
			}
			for (ed.Scrollbar.Y+area.H)-cursor_pos.Y < row_height {
				ed.Scrollbar.Y = ed.Scrollbar.Y + row_height
			}
		} else {
			ed.Scrollbar.Y = 0
		}
	}

	if !ed.SingleLine {
		/* scrollbar widget */
		var scroll rect.Rect
		scroll.X = (area.X + area.W) - style.ScrollbarSize.X
		scroll.Y = area.Y
		scroll.W = style.ScrollbarSize.X
		scroll.H = area.H

		scroll_offset := float64(ed.Scrollbar.Y)
		scroll_step := float64(scroll.H) * 0.1
		scroll_inc := float64(scroll.H) * 0.01
		scroll_target := float64(d.TextSize.Y + row_height)
		ed.Scrollbar.Y = int(doScrollbarv(ed.win, scroll, bounds, scroll_offset, scroll_target, scroll_step, scroll_inc, &style.Scrollbar, inp, font))
	}

	return ret
}

type drawableTextEditor struct {
	Edit      *TextEditor
	State     nstyle.WidgetStates
	Style     *nstyle.Edit
	Scaling   float64
	Bounds    rect.Rect
	Area      rect.Rect
	RowHeight int
	hasInput  bool

	SelectionBegin, SelectionEnd int

	TextSize  image.Point
	CursorPos image.Point
}

func (d *drawableTextEditor) Draw(z *nstyle.Style, out *command.Buffer) {
	edit := d.Edit
	state := d.State
	style := d.Style
	bounds := d.Bounds
	font := z.Font
	area := d.Area
	row_height := d.RowHeight
	selection_begin := d.SelectionBegin
	selection_end := d.SelectionEnd
	if edit.drawchunks != nil {
		edit.drawchunks = edit.drawchunks[:0]
	}

	/* select background colors/images  */
	var old_clip rect.Rect = out.Clip
	{
		var background *nstyle.Item
		if state&nstyle.WidgetStateActive != 0 {
			background = &style.Active
		} else if state&nstyle.WidgetStateHovered != 0 {
			background = &style.Hover
		} else {
			background = &style.Normal
		}

		/* draw background frame */
		if background.Type == nstyle.ItemColor {
			out.FillRect(bounds, style.Rounding, style.BorderColor)
			out.FillRect(shrinkRect(bounds, style.Border), style.Rounding, background.Data.Color)
		} else {
			out.DrawImage(bounds, background.Data.Image)
		}
	}

	area.W -= FontWidth(font, "i")
	clip := unify(old_clip, area)
	out.PushScissor(clip)
	/* draw text */
	var background_color color.RGBA
	var text_color color.RGBA
	var sel_background_color color.RGBA
	var sel_text_color color.RGBA
	var cursor_color color.RGBA
	var cursor_text_color color.RGBA
	var background *nstyle.Item

	/* select correct colors to draw */
	if state&nstyle.WidgetStateActive != 0 {
		background = &style.Active
		text_color = style.TextActive
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_color = style.CursorHover
		cursor_text_color = style.CursorTextHover
	} else if state&nstyle.WidgetStateHovered != 0 {
		background = &style.Hover
		text_color = style.TextHover
		sel_text_color = style.SelectedTextHover
		sel_background_color = style.SelectedHover
		cursor_text_color = style.CursorTextHover
		cursor_color = style.CursorHover
	} else {
		background = &style.Normal
		text_color = style.TextNormal
		sel_text_color = style.SelectedTextNormal
		sel_background_color = style.SelectedNormal
		cursor_color = style.CursorNormal
		cursor_text_color = style.CursorTextNormal
	}

	if background.Type == nstyle.ItemImage {
		background_color = color.RGBA{0, 0, 0, 0}
	} else {
		background_color = background.Data.Color
	}

	startPos := image.Point{area.X - edit.Scrollbar.X, area.Y - edit.Scrollbar.Y}
	pos := startPos
	x_margin := pos.X
	if edit.SelectStart == edit.SelectEnd {
		drawEolCursor := func() {
			cursor_pos := d.CursorPos
			/* draw cursor at end of line */
			var cursor rect.Rect
			if edit.Flags&EditIbeamCursor != 0 {
				cursor.W = int(d.Scaling)
				if cursor.W <= 0 {
					cursor.W = 1
				}
			} else {
				cursor.W = FontWidth(font, "i")
			}
			cursor.H = row_height
			cursor.X = area.X + cursor_pos.X - edit.Scrollbar.X
			cursor.Y = area.Y + cursor_pos.Y + row_height/2.0 - cursor.H/2.0
			cursor.Y -= edit.Scrollbar.Y
			out.FillRect(cursor, 0, cursor_color)
		}

		/* no selection so just draw the complete text */
		pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[:edit.Cursor], 0, row_height, font, background_color, text_color, false)
		d.CursorPos = pos.Sub(startPos)
		if edit.Active && d.hasInput {
			if edit.Cursor < len(edit.Buffer) {
				cursorChar := edit.Buffer[edit.Cursor]
				if cursorChar == '\n' || cursorChar == '\t' || edit.Flags&EditIbeamCursor != 0 {
					pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, background_color, text_color, true)
					drawEolCursor()
				} else {
					pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[edit.Cursor:edit.Cursor+1], edit.Cursor, row_height, font, cursor_color, cursor_text_color, true)
				}
				pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[edit.Cursor+1:], edit.Cursor+1, row_height, font, background_color, text_color, false)
			} else {
				drawEolCursor()
			}
		} else if edit.Cursor < len(edit.Buffer) {
			pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[edit.Cursor:], edit.Cursor, row_height, font, background_color, text_color, false)
		}
	} else {
		/* edit has selection so draw 1-3 text chunks */
		if selection_begin > 0 {
			/* draw unselected text before selection */
			pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[:selection_begin], 0, row_height, font, background_color, text_color, false)
		}

		if selection_begin == edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[selection_begin:selection_end], selection_begin, row_height, font, sel_background_color, sel_text_color, true)

		if selection_begin != edit.SelectEnd {
			d.CursorPos = pos.Sub(startPos)
		}

		if selection_end < len(edit.Buffer) {
			pos = edit.editDrawText(out, style, pos, x_margin, edit.Buffer[selection_end:], selection_end, row_height, font, background_color, text_color, false)
		}
	}
	d.TextSize = pos.Sub(startPos)

	// fix rectangles in drawchunks by subtracting area from them
	for i := range edit.drawchunks {
		edit.drawchunks[i].X -= area.X
		edit.drawchunks[i].Y -= area.Y
	}

	out.PushScissor(old_clip)
}

func runeSliceEquals(a, b []rune) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}

var clipboardModifier = func() key.Modifiers {
	if runtime.GOOS == "darwin" {
		return key.ModMeta
	}
	return key.ModControl
}()

func (edit *TextEditor) popupFind() {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	var searchEd TextEditor
	searchEd.Flags = EditSigEnter | EditClipboard | EditSelectable
	searchEd.Buffer = append(searchEd.Buffer[:0], edit.needle...)
	searchEd.SelectStart = 0
	searchEd.SelectEnd = len(searchEd.Buffer)
	searchEd.Cursor = searchEd.SelectEnd
	searchEd.Active = true

	edit.SelectEnd = edit.SelectStart
	edit.Cursor = edit.SelectStart

	edit.win.Master().PopupOpen("Search...", WindowTitle|WindowNoScrollbar|WindowMovable|WindowBorder|WindowDynamic, rect.Rect{100, 100, 400, 500}, true, func(w *Window) {
		w.Row(30).Static()
		w.LayoutFitWidth(0, 30)
		w.Label("Search: ", "LC")
		w.LayoutSetWidth(150)
		ev := searchEd.Edit(w)
		if ev&EditCommitted != 0 {
			edit.Active = true
			w.Close()
		}
		w.LayoutSetWidth(100)
		if w.ButtonText("Done") {
			edit.Active = true
			w.Close()
		}
		kbd := &w.Input().Keyboard
		for _, k := range kbd.Keys {
			switch {
			case k.Modifiers == clipboardModifier && k.Code == key.CodeG:
				edit.lookForward(true)
			case k.Modifiers == 0 && k.Code == key.CodeEscape:
				edit.SelectEnd = edit.SelectStart
				edit.Cursor = edit.SelectStart
				edit.Active = true
				w.Close()
			}
		}
		if !runeSliceEquals(searchEd.Buffer, edit.needle) {
			edit.needle = append(edit.needle[:0], searchEd.Buffer...)
			edit.lookForward(false)
		}
	})
}

func (edit *TextEditor) lookForward(forceAdvance bool) {
	if edit.Flags&EditMultiline == 0 {
		return
	}
	if edit.hasSelection() {
		if forceAdvance {
			edit.SelectStart = edit.SelectEnd
		} else {
			edit.SelectEnd = edit.SelectStart
		}
		if edit.SelectEnd >= 0 {
			edit.Cursor = edit.SelectEnd
		}
	}
	for start := edit.Cursor; start < len(edit.Buffer); start++ {
		found := true
		for i := 0; i < len(edit.needle); i++ {
			if edit.needle[i] != edit.Buffer[start+i] {
				found = false
				break
			}
		}
		if found {
			edit.SelectStart = start
			edit.SelectEnd = start + len(edit.needle)
			edit.Cursor = edit.SelectEnd
			edit.CursorFollow = true
			return
		}
	}
	edit.SelectStart = 0
	edit.SelectEnd = 0
	edit.Cursor = 0
	edit.CursorFollow = true
}

// Adds text editor edit to win.
// Initial contents of the text editor will be set to text. If
// alwaysSet is specified the contents of the editor will be reset
// to text.
func (edit *TextEditor) Edit(win *Window) EditEvents {
	edit.init(win)
	if edit.Maxlen > 0 {
		if len(edit.Buffer) > edit.Maxlen {
			edit.Buffer = edit.Buffer[:edit.Maxlen]
		}
	}

	if edit.Flags&EditNoCursor != 0 {
		edit.Cursor = len(edit.Buffer)
	}
	if edit.Flags&EditSelectable == 0 {
		edit.SelectStart = edit.Cursor
		edit.SelectEnd = edit.Cursor
	}

	var bounds rect.Rect

	style := &edit.win.ctx.Style
	widget_state, bounds, _ := edit.win.widget()
	if !widget_state {
		return 0
	}
	in := edit.win.inputMaybe(widget_state)

	var cut, copy, paste bool

	if w := win.ContextualOpen(0, image.Point{}, bounds, nil); w != nil {
		w.Row(20).Dynamic(1)
		visible := false
		if edit.Flags&EditClipboard != 0 {
			visible = true
			if w.MenuItem(label.TA("Cut", "LC")) {
				cut = true
			}
			if w.MenuItem(label.TA("Copy", "LC")) {
				copy = true
			}
			if w.MenuItem(label.TA("Paste", "LC")) {
				paste = true
			}
		}
		if edit.Flags&EditMultiline != 0 {
			visible = true
			if w.MenuItem(label.TA("Find...", "LC")) {
				edit.popupFind()
			}
		}
		if !visible {
			w.Close()
		}
	}

	ev := edit.doEdit(bounds, &style.Edit, in, cut, copy, paste)
	return ev
}
