1// Package lineedit is the one-line text editor behind every text field of2// the UI. It depends on nothing in package ui, so components in their own3// packages can edit the shared line.4package lineedit56import (7 "slices"8 "strings"9 "unicode"1011 tea "charm.land/bubbletea/v2"12 "github.com/charmbracelet/x/ansi"13)1415// Line is the one-line text editor behind every text field: the16// entry typed into a cell (ENTER, EDIT), prompts on the context line, and17// the search fields of the find bar and the pickers. Only one of them is18// edited at a time, so they share it; a component with several fields19// keeps the others' text itself and loads a field here to edit it.20type Line struct {21 Buf []rune22 Pos int // the caret, an index into Buf23}2425// Set replaces the text, with the caret at its end.26func (l *Line) Set(text string) {27 l.Buf = []rune(text)28 l.Pos = len(l.Buf)29}3031// Clear empties the line.32func (l *Line) Clear() {33 l.Buf, l.Pos = nil, 034}3536// Text is the whole line.37func (l *Line) Text() string { return string(l.Buf) }3839// Head and tail are the text before and after the caret.40func (l *Line) Head() string { return string(l.Buf[:l.Pos]) }41func (l *Line) Tail() string { return string(l.Buf[l.Pos:]) }4243// Insert types text at the caret. Line breaks and tabs become spaces, and44// other control characters are dropped.45func (l *Line) Insert(text string) {46 for _, r := range text {47 if r == '\n' || r == '\r' || r == '\t' {48 r = ' '49 }50 if !unicode.IsPrint(r) {51 continue52 }53 l.Buf = slices.Insert(l.Buf, l.Pos, r)54 l.Pos++55 }56}5758// Key applies a line-editing Key.59func (l *Line) Key(k tea.KeyPressMsg) {60 if l.wordKey(k.String()) {61 return62 }63 switch k.String() {64 case "left":65 l.Pos = max(l.Pos-1, 0)66 case "right":67 l.Pos = min(l.Pos+1, len(l.Buf))68 case "home", "ctrl+a":69 l.Pos = 070 case "end", "ctrl+e":71 l.Pos = len(l.Buf)72 case "backspace":73 if l.Pos > 0 {74 l.Buf = slices.Delete(l.Buf, l.Pos-1, l.Pos)75 l.Pos--76 }77 case "delete":78 if l.Pos < len(l.Buf) {79 l.Buf = slices.Delete(l.Buf, l.Pos, l.Pos+1)80 }81 default:82 l.Insert(Typed(k))83 }84}8586// SetCaret puts the edit caret at display column x of the entry.87func (l *Line) SetCaret(x int) {88 w := 089 for i, r := range l.Buf {90 if w >= x {91 l.Pos = i92 return93 }94 w += ansi.StringWidth(string(r))95 }96 l.Pos = len(l.Buf)97}9899// IsFormula reports whether the edit line holds a formula.100func (l *Line) IsFormula() bool {101 return len(l.Buf) > 0 && (l.Buf[0] == '=' || l.Buf[0] == '+' || l.Buf[0] == '-')102}103104// CanPoint reports whether the text before the caret ends where a cell105// reference may follow.106func (l *Line) CanPoint() bool {107 if l.Pos == 0 {108 return false109 }110 return strings.ContainsRune("=+-*/^(,;<>&:", l.Buf[l.Pos-1])111}112113// Typed returns the printable text of a key press, if any.114func Typed(k tea.KeyPressMsg) string {115 if k.Mod.Contains(tea.ModCtrl) || k.Mod.Contains(tea.ModAlt) {116 return ""117 }118 return k.Text119}