textarea.go 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343
  1. package textarea
  2. import (
  3. "crypto/sha256"
  4. "fmt"
  5. "image/color"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "unicode"
  10. "slices"
  11. "github.com/charmbracelet/bubbles/v2/cursor"
  12. "github.com/charmbracelet/bubbles/v2/key"
  13. tea "github.com/charmbracelet/bubbletea/v2"
  14. "github.com/charmbracelet/lipgloss/v2"
  15. "github.com/charmbracelet/x/ansi"
  16. rw "github.com/mattn/go-runewidth"
  17. "github.com/rivo/uniseg"
  18. "github.com/sst/opencode/internal/attachment"
  19. )
  20. const (
  21. minHeight = 1
  22. defaultHeight = 1
  23. defaultWidth = 40
  24. defaultCharLimit = 0 // no limit
  25. defaultMaxHeight = 99
  26. defaultMaxWidth = 500
  27. // XXX: in v2, make max lines dynamic and default max lines configurable.
  28. maxLines = 10000
  29. )
  30. // Helper functions for converting between runes and any slices
  31. // runesToInterfaces converts a slice of runes to a slice of interfaces
  32. func runesToInterfaces(runes []rune) []any {
  33. result := make([]any, len(runes))
  34. for i, r := range runes {
  35. result[i] = r
  36. }
  37. return result
  38. }
  39. // interfacesToRunes converts a slice of interfaces to a slice of runes (for display purposes)
  40. func interfacesToRunes(items []any) []rune {
  41. var result []rune
  42. for _, item := range items {
  43. switch val := item.(type) {
  44. case rune:
  45. result = append(result, val)
  46. case *attachment.Attachment:
  47. result = append(result, []rune(val.Display)...)
  48. }
  49. }
  50. return result
  51. }
  52. // copyInterfaceSlice creates a copy of an any slice
  53. func copyInterfaceSlice(src []any) []any {
  54. dst := make([]any, len(src))
  55. copy(dst, src)
  56. return dst
  57. }
  58. // interfacesToString converts a slice of interfaces to a string for display
  59. func interfacesToString(items []any) string {
  60. var s strings.Builder
  61. for _, item := range items {
  62. switch val := item.(type) {
  63. case rune:
  64. s.WriteRune(val)
  65. case *attachment.Attachment:
  66. s.WriteString(val.Display)
  67. }
  68. }
  69. return s.String()
  70. }
  71. // isAttachmentAtCursor checks if the cursor is positioned on or immediately after an attachment.
  72. // This allows for proper highlighting even when the cursor is technically at the position
  73. // after the attachment object in the underlying slice.
  74. func (m Model) isAttachmentAtCursor() (*attachment.Attachment, int, int) {
  75. if m.row >= len(m.value) {
  76. return nil, -1, -1
  77. }
  78. row := m.value[m.row]
  79. col := m.col
  80. if col < 0 || col > len(row) {
  81. return nil, -1, -1
  82. }
  83. // Check if the cursor is at the same index as an attachment.
  84. if col < len(row) {
  85. if att, ok := row[col].(*attachment.Attachment); ok {
  86. return att, col, col
  87. }
  88. }
  89. // Check if the cursor is immediately after an attachment. This is a common
  90. // state, for example, after just inserting one.
  91. if col > 0 && col <= len(row) {
  92. if att, ok := row[col-1].(*attachment.Attachment); ok {
  93. return att, col - 1, col - 1
  94. }
  95. }
  96. return nil, -1, -1
  97. }
  98. // renderLineWithAttachments renders a line with proper attachment highlighting
  99. func (m Model) renderLineWithAttachments(
  100. items []any,
  101. style lipgloss.Style,
  102. ) string {
  103. var s strings.Builder
  104. currentAttachment, _, _ := m.isAttachmentAtCursor()
  105. for _, item := range items {
  106. switch val := item.(type) {
  107. case rune:
  108. s.WriteString(style.Render(string(val)))
  109. case *attachment.Attachment:
  110. // Check if this is the attachment the cursor is currently on
  111. if currentAttachment != nil && currentAttachment.ID == val.ID {
  112. // Cursor is on this attachment, highlight it
  113. s.WriteString(m.Styles.SelectedAttachment.Render(val.Display))
  114. } else {
  115. s.WriteString(m.Styles.Attachment.Render(val.Display))
  116. }
  117. }
  118. }
  119. return s.String()
  120. }
  121. // getRuneAt safely gets a rune at a specific position, returns 0 if not a rune
  122. func getRuneAt(items []any, index int) rune {
  123. if index < 0 || index >= len(items) {
  124. return 0
  125. }
  126. if r, ok := items[index].(rune); ok {
  127. return r
  128. }
  129. return 0
  130. }
  131. // isSpaceAt checks if the item at index is a space rune
  132. func isSpaceAt(items []any, index int) bool {
  133. r := getRuneAt(items, index)
  134. return r != 0 && unicode.IsSpace(r)
  135. }
  136. // setRuneAt safely sets a rune at a specific position if it's a rune
  137. func setRuneAt(items []any, index int, r rune) {
  138. if index >= 0 && index < len(items) {
  139. if _, ok := items[index].(rune); ok {
  140. items[index] = r
  141. }
  142. }
  143. }
  144. // Internal messages for clipboard operations.
  145. type (
  146. pasteMsg string
  147. pasteErrMsg struct{ error }
  148. )
  149. // KeyMap is the key bindings for different actions within the textarea.
  150. type KeyMap struct {
  151. CharacterBackward key.Binding
  152. CharacterForward key.Binding
  153. DeleteAfterCursor key.Binding
  154. DeleteBeforeCursor key.Binding
  155. DeleteCharacterBackward key.Binding
  156. DeleteCharacterForward key.Binding
  157. DeleteWordBackward key.Binding
  158. DeleteWordForward key.Binding
  159. InsertNewline key.Binding
  160. LineEnd key.Binding
  161. LineNext key.Binding
  162. LinePrevious key.Binding
  163. LineStart key.Binding
  164. Paste key.Binding
  165. WordBackward key.Binding
  166. WordForward key.Binding
  167. InputBegin key.Binding
  168. InputEnd key.Binding
  169. UppercaseWordForward key.Binding
  170. LowercaseWordForward key.Binding
  171. CapitalizeWordForward key.Binding
  172. TransposeCharacterBackward key.Binding
  173. }
  174. // DefaultKeyMap returns the default set of key bindings for navigating and acting
  175. // upon the textarea.
  176. func DefaultKeyMap() KeyMap {
  177. return KeyMap{
  178. CharacterForward: key.NewBinding(
  179. key.WithKeys("right", "ctrl+f"),
  180. key.WithHelp("right", "character forward"),
  181. ),
  182. CharacterBackward: key.NewBinding(
  183. key.WithKeys("left", "ctrl+b"),
  184. key.WithHelp("left", "character backward"),
  185. ),
  186. WordForward: key.NewBinding(
  187. key.WithKeys("alt+right", "ctrl+right", "alt+f"),
  188. key.WithHelp("alt+right", "word forward"),
  189. ),
  190. WordBackward: key.NewBinding(
  191. key.WithKeys("alt+left", "ctrl+left", "alt+b"),
  192. key.WithHelp("alt+left", "word backward"),
  193. ),
  194. LineNext: key.NewBinding(
  195. key.WithKeys("down", "ctrl+n"),
  196. key.WithHelp("down", "next line"),
  197. ),
  198. LinePrevious: key.NewBinding(
  199. key.WithKeys("up", "ctrl+p"),
  200. key.WithHelp("up", "previous line"),
  201. ),
  202. DeleteWordBackward: key.NewBinding(
  203. key.WithKeys("alt+backspace", "ctrl+w"),
  204. key.WithHelp("alt+backspace", "delete word backward"),
  205. ),
  206. DeleteWordForward: key.NewBinding(
  207. key.WithKeys("alt+delete", "alt+d"),
  208. key.WithHelp("alt+delete", "delete word forward"),
  209. ),
  210. DeleteAfterCursor: key.NewBinding(
  211. key.WithKeys("ctrl+k"),
  212. key.WithHelp("ctrl+k", "delete after cursor"),
  213. ),
  214. DeleteBeforeCursor: key.NewBinding(
  215. key.WithKeys("ctrl+u"),
  216. key.WithHelp("ctrl+u", "delete before cursor"),
  217. ),
  218. InsertNewline: key.NewBinding(
  219. key.WithKeys("enter", "ctrl+m"),
  220. key.WithHelp("enter", "insert newline"),
  221. ),
  222. DeleteCharacterBackward: key.NewBinding(
  223. key.WithKeys("backspace", "ctrl+h"),
  224. key.WithHelp("backspace", "delete character backward"),
  225. ),
  226. DeleteCharacterForward: key.NewBinding(
  227. key.WithKeys("delete", "ctrl+d"),
  228. key.WithHelp("delete", "delete character forward"),
  229. ),
  230. LineStart: key.NewBinding(
  231. key.WithKeys("home", "ctrl+a"),
  232. key.WithHelp("home", "line start"),
  233. ),
  234. LineEnd: key.NewBinding(
  235. key.WithKeys("end", "ctrl+e"),
  236. key.WithHelp("end", "line end"),
  237. ),
  238. Paste: key.NewBinding(
  239. key.WithKeys("ctrl+v"),
  240. key.WithHelp("ctrl+v", "paste"),
  241. ),
  242. InputBegin: key.NewBinding(
  243. key.WithKeys("alt+<", "ctrl+home"),
  244. key.WithHelp("alt+<", "input begin"),
  245. ),
  246. InputEnd: key.NewBinding(
  247. key.WithKeys("alt+>", "ctrl+end"),
  248. key.WithHelp("alt+>", "input end"),
  249. ),
  250. CapitalizeWordForward: key.NewBinding(
  251. key.WithKeys("alt+c"),
  252. key.WithHelp("alt+c", "capitalize word forward"),
  253. ),
  254. LowercaseWordForward: key.NewBinding(
  255. key.WithKeys("alt+l"),
  256. key.WithHelp("alt+l", "lowercase word forward"),
  257. ),
  258. UppercaseWordForward: key.NewBinding(
  259. key.WithKeys("alt+u"),
  260. key.WithHelp("alt+u", "uppercase word forward"),
  261. ),
  262. TransposeCharacterBackward: key.NewBinding(
  263. key.WithKeys("ctrl+t"),
  264. key.WithHelp("ctrl+t", "transpose character backward"),
  265. ),
  266. }
  267. }
  268. // LineInfo is a helper for keeping track of line information regarding
  269. // soft-wrapped lines.
  270. type LineInfo struct {
  271. // Width is the number of columns in the line.
  272. Width int
  273. // CharWidth is the number of characters in the line to account for
  274. // double-width runes.
  275. CharWidth int
  276. // Height is the number of rows in the line.
  277. Height int
  278. // StartColumn is the index of the first column of the line.
  279. StartColumn int
  280. // ColumnOffset is the number of columns that the cursor is offset from the
  281. // start of the line.
  282. ColumnOffset int
  283. // RowOffset is the number of rows that the cursor is offset from the start
  284. // of the line.
  285. RowOffset int
  286. // CharOffset is the number of characters that the cursor is offset
  287. // from the start of the line. This will generally be equivalent to
  288. // ColumnOffset, but will be different there are double-width runes before
  289. // the cursor.
  290. CharOffset int
  291. }
  292. // CursorStyle is the style for real and virtual cursors.
  293. type CursorStyle struct {
  294. // Style styles the cursor block.
  295. //
  296. // For real cursors, the foreground color set here will be used as the
  297. // cursor color.
  298. Color color.Color
  299. // Shape is the cursor shape. The following shapes are available:
  300. //
  301. // - tea.CursorBlock
  302. // - tea.CursorUnderline
  303. // - tea.CursorBar
  304. //
  305. // This is only used for real cursors.
  306. Shape tea.CursorShape
  307. // CursorBlink determines whether or not the cursor should blink.
  308. Blink bool
  309. // BlinkSpeed is the speed at which the virtual cursor blinks. This has no
  310. // effect on real cursors as well as no effect if the cursor is set not to
  311. // [CursorBlink].
  312. //
  313. // By default, the blink speed is set to about 500ms.
  314. BlinkSpeed time.Duration
  315. }
  316. // Styles are the styles for the textarea, separated into focused and blurred
  317. // states. The appropriate styles will be chosen based on the focus state of
  318. // the textarea.
  319. type Styles struct {
  320. Focused StyleState
  321. Blurred StyleState
  322. Cursor CursorStyle
  323. Attachment lipgloss.Style
  324. SelectedAttachment lipgloss.Style
  325. }
  326. // StyleState that will be applied to the text area.
  327. //
  328. // StyleState can be applied to focused and unfocused states to change the styles
  329. // depending on the focus state.
  330. //
  331. // For an introduction to styling with Lip Gloss see:
  332. // https://github.com/charmbracelet/lipgloss
  333. type StyleState struct {
  334. Base lipgloss.Style
  335. Text lipgloss.Style
  336. LineNumber lipgloss.Style
  337. CursorLineNumber lipgloss.Style
  338. CursorLine lipgloss.Style
  339. EndOfBuffer lipgloss.Style
  340. Placeholder lipgloss.Style
  341. Prompt lipgloss.Style
  342. }
  343. func (s StyleState) computedCursorLine() lipgloss.Style {
  344. return s.CursorLine.Inherit(s.Base).Inline(true)
  345. }
  346. func (s StyleState) computedCursorLineNumber() lipgloss.Style {
  347. return s.CursorLineNumber.
  348. Inherit(s.CursorLine).
  349. Inherit(s.Base).
  350. Inline(true)
  351. }
  352. func (s StyleState) computedEndOfBuffer() lipgloss.Style {
  353. return s.EndOfBuffer.Inherit(s.Base).Inline(true)
  354. }
  355. func (s StyleState) computedLineNumber() lipgloss.Style {
  356. return s.LineNumber.Inherit(s.Base).Inline(true)
  357. }
  358. func (s StyleState) computedPlaceholder() lipgloss.Style {
  359. return s.Placeholder.Inherit(s.Base).Inline(true)
  360. }
  361. func (s StyleState) computedPrompt() lipgloss.Style {
  362. return s.Prompt.Inherit(s.Base).Inline(true)
  363. }
  364. func (s StyleState) computedText() lipgloss.Style {
  365. return s.Text.Inherit(s.Base).Inline(true)
  366. }
  367. // line is the input to the text wrapping function. This is stored in a struct
  368. // so that it can be hashed and memoized.
  369. type line struct {
  370. content []any // Contains runes and *Attachment
  371. width int
  372. }
  373. // Hash returns a hash of the line.
  374. func (w line) Hash() string {
  375. var s strings.Builder
  376. for _, item := range w.content {
  377. switch v := item.(type) {
  378. case rune:
  379. s.WriteRune(v)
  380. case *attachment.Attachment:
  381. s.WriteString(v.ID)
  382. }
  383. }
  384. v := fmt.Sprintf("%s:%d", s.String(), w.width)
  385. return fmt.Sprintf("%x", sha256.Sum256([]byte(v)))
  386. }
  387. // Model is the Bubble Tea model for this text area element.
  388. type Model struct {
  389. Err error
  390. // General settings.
  391. cache *MemoCache[line, [][]any]
  392. // Prompt is printed at the beginning of each line.
  393. //
  394. // When changing the value of Prompt after the model has been
  395. // initialized, ensure that SetWidth() gets called afterwards.
  396. //
  397. // See also [SetPromptFunc] for a dynamic prompt.
  398. Prompt string
  399. // Placeholder is the text displayed when the user
  400. // hasn't entered anything yet.
  401. Placeholder string
  402. // ShowLineNumbers, if enabled, causes line numbers to be printed
  403. // after the prompt.
  404. ShowLineNumbers bool
  405. // EndOfBufferCharacter is displayed at the end of the input.
  406. EndOfBufferCharacter rune
  407. // KeyMap encodes the keybindings recognized by the widget.
  408. KeyMap KeyMap
  409. // Styling. FocusedStyle and BlurredStyle are used to style the textarea in
  410. // focused and blurred states.
  411. Styles Styles
  412. // virtualCursor manages the virtual cursor.
  413. virtualCursor cursor.Model
  414. // VirtualCursor determines whether or not to use the virtual cursor. If
  415. // set to false, use [Model.Cursor] to return a real cursor for rendering.
  416. VirtualCursor bool
  417. // CharLimit is the maximum number of characters this input element will
  418. // accept. If 0 or less, there's no limit.
  419. CharLimit int
  420. // MaxHeight is the maximum height of the text area in rows. If 0 or less,
  421. // there's no limit.
  422. MaxHeight int
  423. // MaxWidth is the maximum width of the text area in columns. If 0 or less,
  424. // there's no limit.
  425. MaxWidth int
  426. // If promptFunc is set, it replaces Prompt as a generator for
  427. // prompt strings at the beginning of each line.
  428. promptFunc func(line int) string
  429. // promptWidth is the width of the prompt.
  430. promptWidth int
  431. // width is the maximum number of characters that can be displayed at once.
  432. // If 0 or less this setting is ignored.
  433. width int
  434. // height is the maximum number of lines that can be displayed at once. It
  435. // essentially treats the text field like a vertically scrolling viewport
  436. // if there are more lines than the permitted height.
  437. height int
  438. // Underlying text value. Contains either rune or *Attachment types.
  439. value [][]any
  440. // focus indicates whether user input focus should be on this input
  441. // component. When false, ignore keyboard input and hide the cursor.
  442. focus bool
  443. // Cursor column (slice index).
  444. col int
  445. // Cursor row.
  446. row int
  447. // Last character offset, used to maintain state when the cursor is moved
  448. // vertically such that we can maintain the same navigating position.
  449. lastCharOffset int
  450. // rune sanitizer for input.
  451. rsan Sanitizer
  452. }
  453. // New creates a new model with default settings.
  454. func New() Model {
  455. cur := cursor.New()
  456. styles := DefaultDarkStyles()
  457. m := Model{
  458. CharLimit: defaultCharLimit,
  459. MaxHeight: defaultMaxHeight,
  460. MaxWidth: defaultMaxWidth,
  461. Prompt: lipgloss.ThickBorder().Left + " ",
  462. Styles: styles,
  463. cache: NewMemoCache[line, [][]any](maxLines),
  464. EndOfBufferCharacter: ' ',
  465. ShowLineNumbers: true,
  466. VirtualCursor: true,
  467. virtualCursor: cur,
  468. KeyMap: DefaultKeyMap(),
  469. value: make([][]any, minHeight, maxLines),
  470. focus: false,
  471. col: 0,
  472. row: 0,
  473. }
  474. m.SetWidth(defaultWidth)
  475. m.SetHeight(defaultHeight)
  476. return m
  477. }
  478. // DefaultStyles returns the default styles for focused and blurred states for
  479. // the textarea.
  480. func DefaultStyles(isDark bool) Styles {
  481. lightDark := lipgloss.LightDark(isDark)
  482. var s Styles
  483. s.Focused = StyleState{
  484. Base: lipgloss.NewStyle(),
  485. CursorLine: lipgloss.NewStyle().
  486. Background(lightDark(lipgloss.Color("255"), lipgloss.Color("0"))),
  487. CursorLineNumber: lipgloss.NewStyle().
  488. Foreground(lightDark(lipgloss.Color("240"), lipgloss.Color("240"))),
  489. EndOfBuffer: lipgloss.NewStyle().
  490. Foreground(lightDark(lipgloss.Color("254"), lipgloss.Color("0"))),
  491. LineNumber: lipgloss.NewStyle().
  492. Foreground(lightDark(lipgloss.Color("249"), lipgloss.Color("7"))),
  493. Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
  494. Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
  495. Text: lipgloss.NewStyle(),
  496. }
  497. s.Blurred = StyleState{
  498. Base: lipgloss.NewStyle(),
  499. CursorLine: lipgloss.NewStyle().
  500. Foreground(lightDark(lipgloss.Color("245"), lipgloss.Color("7"))),
  501. CursorLineNumber: lipgloss.NewStyle().
  502. Foreground(lightDark(lipgloss.Color("249"), lipgloss.Color("7"))),
  503. EndOfBuffer: lipgloss.NewStyle().
  504. Foreground(lightDark(lipgloss.Color("254"), lipgloss.Color("0"))),
  505. LineNumber: lipgloss.NewStyle().
  506. Foreground(lightDark(lipgloss.Color("249"), lipgloss.Color("7"))),
  507. Placeholder: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
  508. Prompt: lipgloss.NewStyle().Foreground(lipgloss.Color("7")),
  509. Text: lipgloss.NewStyle().
  510. Foreground(lightDark(lipgloss.Color("245"), lipgloss.Color("7"))),
  511. }
  512. s.Attachment = lipgloss.NewStyle().
  513. Background(lipgloss.Color("11")).
  514. Foreground(lipgloss.Color("0"))
  515. s.SelectedAttachment = lipgloss.NewStyle().
  516. Background(lipgloss.Color("11")).
  517. Foreground(lipgloss.Color("0"))
  518. s.Cursor = CursorStyle{
  519. Color: lipgloss.Color("7"),
  520. Shape: tea.CursorBlock,
  521. Blink: true,
  522. }
  523. return s
  524. }
  525. // DefaultLightStyles returns the default styles for a light background.
  526. func DefaultLightStyles() Styles {
  527. return DefaultStyles(false)
  528. }
  529. // DefaultDarkStyles returns the default styles for a dark background.
  530. func DefaultDarkStyles() Styles {
  531. return DefaultStyles(true)
  532. }
  533. // updateVirtualCursorStyle sets styling on the virtual cursor based on the
  534. // textarea's style settings.
  535. func (m *Model) updateVirtualCursorStyle() {
  536. if !m.VirtualCursor {
  537. m.virtualCursor.SetMode(cursor.CursorHide)
  538. return
  539. }
  540. m.virtualCursor.Style = lipgloss.NewStyle().Foreground(m.Styles.Cursor.Color)
  541. // By default, the blink speed of the cursor is set to a default
  542. // internally.
  543. if m.Styles.Cursor.Blink {
  544. if m.Styles.Cursor.BlinkSpeed > 0 {
  545. m.virtualCursor.BlinkSpeed = m.Styles.Cursor.BlinkSpeed
  546. }
  547. m.virtualCursor.SetMode(cursor.CursorBlink)
  548. return
  549. }
  550. m.virtualCursor.SetMode(cursor.CursorStatic)
  551. }
  552. // SetValue sets the value of the text input.
  553. func (m *Model) SetValue(s string) {
  554. m.Reset()
  555. m.InsertString(s)
  556. }
  557. // InsertString inserts a string at the cursor position.
  558. func (m *Model) InsertString(s string) {
  559. m.InsertRunesFromUserInput([]rune(s))
  560. }
  561. // InsertRune inserts a rune at the cursor position.
  562. func (m *Model) InsertRune(r rune) {
  563. m.InsertRunesFromUserInput([]rune{r})
  564. }
  565. // InsertAttachment inserts an attachment at the cursor position.
  566. func (m *Model) InsertAttachment(att *attachment.Attachment) {
  567. if m.CharLimit > 0 {
  568. availSpace := m.CharLimit - m.Length()
  569. // If the char limit's been reached, cancel.
  570. if availSpace <= 0 {
  571. return
  572. }
  573. }
  574. // Insert the attachment at the current cursor position
  575. m.value[m.row] = append(
  576. m.value[m.row][:m.col],
  577. append([]any{att}, m.value[m.row][m.col:]...)...)
  578. m.col++
  579. m.SetCursorColumn(m.col)
  580. }
  581. // ReplaceRange replaces text from startCol to endCol on the current row with the given string.
  582. // This preserves attachments outside the replaced range.
  583. func (m *Model) ReplaceRange(startCol, endCol int, replacement string) {
  584. if m.row >= len(m.value) || startCol < 0 || endCol < startCol {
  585. return
  586. }
  587. // Ensure bounds are within the current row
  588. rowLen := len(m.value[m.row])
  589. startCol = max(0, min(startCol, rowLen))
  590. endCol = max(startCol, min(endCol, rowLen))
  591. // Create new row content: before + replacement + after
  592. before := m.value[m.row][:startCol]
  593. after := m.value[m.row][endCol:]
  594. replacementRunes := runesToInterfaces([]rune(replacement))
  595. // Combine the parts
  596. newRow := make([]any, 0, len(before)+len(replacementRunes)+len(after))
  597. newRow = append(newRow, before...)
  598. newRow = append(newRow, replacementRunes...)
  599. newRow = append(newRow, after...)
  600. m.value[m.row] = newRow
  601. // Position cursor at end of replacement
  602. m.col = startCol + len(replacementRunes)
  603. m.SetCursorColumn(m.col)
  604. }
  605. // CurrentRowLength returns the length of the current row.
  606. func (m *Model) CurrentRowLength() int {
  607. if m.row >= len(m.value) {
  608. return 0
  609. }
  610. return len(m.value[m.row])
  611. }
  612. // GetAttachments returns all attachments in the textarea with accurate position indices.
  613. func (m Model) GetAttachments() []*attachment.Attachment {
  614. var attachments []*attachment.Attachment
  615. position := 0 // Track absolute position in the text
  616. for rowIdx, row := range m.value {
  617. colPosition := 0 // Track position within the current row
  618. for _, item := range row {
  619. switch v := item.(type) {
  620. case *attachment.Attachment:
  621. // Clone the attachment to avoid modifying the original
  622. att := *v
  623. att.StartIndex = position + colPosition
  624. att.EndIndex = position + colPosition + len(v.Display)
  625. attachments = append(attachments, &att)
  626. colPosition += len(v.Display)
  627. case rune:
  628. colPosition++
  629. }
  630. }
  631. // Add newline character position (except for last row)
  632. if rowIdx < len(m.value)-1 {
  633. position += colPosition + 1 // +1 for newline
  634. } else {
  635. position += colPosition
  636. }
  637. }
  638. return attachments
  639. }
  640. // InsertRunesFromUserInput inserts runes at the current cursor position.
  641. func (m *Model) InsertRunesFromUserInput(runes []rune) {
  642. // Clean up any special characters in the input provided by the
  643. // clipboard. This avoids bugs due to e.g. tab characters and
  644. // whatnot.
  645. runes = m.san().Sanitize(runes)
  646. if m.CharLimit > 0 {
  647. availSpace := m.CharLimit - m.Length()
  648. // If the char limit's been reached, cancel.
  649. if availSpace <= 0 {
  650. return
  651. }
  652. // If there's not enough space to paste the whole thing cut the pasted
  653. // runes down so they'll fit.
  654. if availSpace < len(runes) {
  655. runes = runes[:availSpace]
  656. }
  657. }
  658. // Split the input into lines.
  659. var lines [][]rune
  660. lstart := 0
  661. for i := range runes {
  662. if runes[i] == '\n' {
  663. // Queue a line to become a new row in the text area below.
  664. // Beware to clamp the max capacity of the slice, to ensure no
  665. // data from different rows get overwritten when later edits
  666. // will modify this line.
  667. lines = append(lines, runes[lstart:i:i])
  668. lstart = i + 1
  669. }
  670. }
  671. if lstart <= len(runes) {
  672. // The last line did not end with a newline character.
  673. // Take it now.
  674. lines = append(lines, runes[lstart:])
  675. }
  676. // Obey the maximum line limit.
  677. if maxLines > 0 && len(m.value)+len(lines)-1 > maxLines {
  678. allowedHeight := max(0, maxLines-len(m.value)+1)
  679. lines = lines[:allowedHeight]
  680. }
  681. if len(lines) == 0 {
  682. // Nothing left to insert.
  683. return
  684. }
  685. // Save the remainder of the original line at the current
  686. // cursor position.
  687. tail := copyInterfaceSlice(m.value[m.row][m.col:])
  688. // Paste the first line at the current cursor position.
  689. m.value[m.row] = append(m.value[m.row][:m.col], runesToInterfaces(lines[0])...)
  690. m.col += len(lines[0])
  691. if numExtraLines := len(lines) - 1; numExtraLines > 0 {
  692. // Add the new lines.
  693. // We try to reuse the slice if there's already space.
  694. var newGrid [][]any
  695. if cap(m.value) >= len(m.value)+numExtraLines {
  696. // Can reuse the extra space.
  697. newGrid = m.value[:len(m.value)+numExtraLines]
  698. } else {
  699. // No space left; need a new slice.
  700. newGrid = make([][]any, len(m.value)+numExtraLines)
  701. copy(newGrid, m.value[:m.row+1])
  702. }
  703. // Add all the rows that were after the cursor in the original
  704. // grid at the end of the new grid.
  705. copy(newGrid[m.row+1+numExtraLines:], m.value[m.row+1:])
  706. m.value = newGrid
  707. // Insert all the new lines in the middle.
  708. for _, l := range lines[1:] {
  709. m.row++
  710. m.value[m.row] = runesToInterfaces(l)
  711. m.col = len(l)
  712. }
  713. }
  714. // Finally add the tail at the end of the last line inserted.
  715. m.value[m.row] = append(m.value[m.row], tail...)
  716. m.SetCursorColumn(m.col)
  717. }
  718. // Value returns the value of the text input.
  719. func (m Model) Value() string {
  720. if m.value == nil {
  721. return ""
  722. }
  723. var v strings.Builder
  724. for _, l := range m.value {
  725. for _, item := range l {
  726. switch val := item.(type) {
  727. case rune:
  728. v.WriteRune(val)
  729. case *attachment.Attachment:
  730. v.WriteString(val.Display)
  731. }
  732. }
  733. v.WriteByte('\n')
  734. }
  735. return strings.TrimSuffix(v.String(), "\n")
  736. }
  737. // Length returns the number of characters currently in the text input.
  738. func (m *Model) Length() int {
  739. var l int
  740. for _, row := range m.value {
  741. for _, item := range row {
  742. switch val := item.(type) {
  743. case rune:
  744. l += rw.RuneWidth(val)
  745. case *attachment.Attachment:
  746. l += uniseg.StringWidth(val.Display)
  747. }
  748. }
  749. }
  750. // We add len(m.value) to include the newline characters.
  751. return l + len(m.value) - 1
  752. }
  753. // LineCount returns the number of lines that are currently in the text input.
  754. func (m *Model) LineCount() int {
  755. return m.ContentHeight()
  756. }
  757. // Line returns the line position.
  758. func (m Model) Line() int {
  759. return m.row
  760. }
  761. // CursorColumn returns the cursor's column position (slice index).
  762. func (m Model) CursorColumn() int {
  763. return m.col
  764. }
  765. // LastRuneIndex returns the index of the last occurrence of a rune on the current line,
  766. // searching backwards from the current cursor position.
  767. // Returns -1 if the rune is not found before the cursor.
  768. func (m Model) LastRuneIndex(r rune) int {
  769. if m.row >= len(m.value) {
  770. return -1
  771. }
  772. // Iterate backwards from just before the cursor position
  773. for i := m.col - 1; i >= 0; i-- {
  774. if i < len(m.value[m.row]) {
  775. if item, ok := m.value[m.row][i].(rune); ok && item == r {
  776. return i
  777. }
  778. }
  779. }
  780. return -1
  781. }
  782. func (m *Model) Newline() {
  783. if m.MaxHeight > 0 && len(m.value) >= m.MaxHeight {
  784. return
  785. }
  786. m.col = clamp(m.col, 0, len(m.value[m.row]))
  787. m.splitLine(m.row, m.col)
  788. }
  789. // mapVisualOffsetToSliceIndex converts a visual column offset to a slice index.
  790. // This is used to maintain the cursor's horizontal position when moving vertically.
  791. func (m *Model) mapVisualOffsetToSliceIndex(row int, charOffset int) int {
  792. if row < 0 || row >= len(m.value) {
  793. return 0
  794. }
  795. offset := 0
  796. // Find the slice index that corresponds to the visual offset.
  797. for i, item := range m.value[row] {
  798. var itemWidth int
  799. switch v := item.(type) {
  800. case rune:
  801. itemWidth = rw.RuneWidth(v)
  802. case *attachment.Attachment:
  803. itemWidth = uniseg.StringWidth(v.Display)
  804. }
  805. // If the target offset falls within the current item, this is our index.
  806. if offset+itemWidth > charOffset {
  807. // Decide whether to stick with the previous index or move to the current
  808. // one based on which is closer to the target offset.
  809. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  810. return i + 1
  811. }
  812. return i
  813. }
  814. offset += itemWidth
  815. }
  816. return len(m.value[row])
  817. }
  818. // CursorDown moves the cursor down by one line.
  819. func (m *Model) CursorDown() {
  820. li := m.LineInfo()
  821. charOffset := max(m.lastCharOffset, li.CharOffset)
  822. m.lastCharOffset = charOffset
  823. if li.RowOffset+1 >= li.Height && m.row < len(m.value)-1 {
  824. // Move to the next model line
  825. m.row++
  826. // We want to land on the first wrapped line of the new model line.
  827. grid := m.memoizedWrap(m.value[m.row], m.width)
  828. targetLineContent := grid[0]
  829. // Find position within the first wrapped line.
  830. offset := 0
  831. colInLine := 0
  832. for i, item := range targetLineContent {
  833. var itemWidth int
  834. switch v := item.(type) {
  835. case rune:
  836. itemWidth = rw.RuneWidth(v)
  837. case *attachment.Attachment:
  838. itemWidth = uniseg.StringWidth(v.Display)
  839. }
  840. if offset+itemWidth > charOffset {
  841. // Decide whether to stick with the previous index or move to the current
  842. // one based on which is closer to the target offset.
  843. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  844. colInLine = i + 1
  845. } else {
  846. colInLine = i
  847. }
  848. goto foundNextLine
  849. }
  850. offset += itemWidth
  851. }
  852. colInLine = len(targetLineContent)
  853. foundNextLine:
  854. m.col = colInLine // startCol is 0 for the first wrapped line
  855. } else if li.RowOffset+1 < li.Height {
  856. // Move to the next wrapped line within the same model line
  857. grid := m.memoizedWrap(m.value[m.row], m.width)
  858. targetLineContent := grid[li.RowOffset+1]
  859. startCol := 0
  860. for i := 0; i < li.RowOffset+1; i++ {
  861. startCol += len(grid[i])
  862. }
  863. // Find position within the target wrapped line.
  864. offset := 0
  865. colInLine := 0
  866. for i, item := range targetLineContent {
  867. var itemWidth int
  868. switch v := item.(type) {
  869. case rune:
  870. itemWidth = rw.RuneWidth(v)
  871. case *attachment.Attachment:
  872. itemWidth = uniseg.StringWidth(v.Display)
  873. }
  874. if offset+itemWidth > charOffset {
  875. // Decide whether to stick with the previous index or move to the current
  876. // one based on which is closer to the target offset.
  877. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  878. colInLine = i + 1
  879. } else {
  880. colInLine = i
  881. }
  882. goto foundSameLine
  883. }
  884. offset += itemWidth
  885. }
  886. colInLine = len(targetLineContent)
  887. foundSameLine:
  888. m.col = startCol + colInLine
  889. }
  890. m.SetCursorColumn(m.col)
  891. }
  892. // CursorUp moves the cursor up by one line.
  893. func (m *Model) CursorUp() {
  894. li := m.LineInfo()
  895. charOffset := max(m.lastCharOffset, li.CharOffset)
  896. m.lastCharOffset = charOffset
  897. if li.RowOffset <= 0 && m.row > 0 {
  898. // Move to the previous model line. We want to land on the last wrapped
  899. // line of the previous model line.
  900. m.row--
  901. grid := m.memoizedWrap(m.value[m.row], m.width)
  902. targetLineContent := grid[len(grid)-1]
  903. // Find start of last wrapped line.
  904. startCol := len(m.value[m.row]) - len(targetLineContent)
  905. // Find position within the last wrapped line.
  906. offset := 0
  907. colInLine := 0
  908. for i, item := range targetLineContent {
  909. var itemWidth int
  910. switch v := item.(type) {
  911. case rune:
  912. itemWidth = rw.RuneWidth(v)
  913. case *attachment.Attachment:
  914. itemWidth = uniseg.StringWidth(v.Display)
  915. }
  916. if offset+itemWidth > charOffset {
  917. // Decide whether to stick with the previous index or move to the current
  918. // one based on which is closer to the target offset.
  919. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  920. colInLine = i + 1
  921. } else {
  922. colInLine = i
  923. }
  924. goto foundPrevLine
  925. }
  926. offset += itemWidth
  927. }
  928. colInLine = len(targetLineContent)
  929. foundPrevLine:
  930. m.col = startCol + colInLine
  931. } else if li.RowOffset > 0 {
  932. // Move to the previous wrapped line within the same model line.
  933. grid := m.memoizedWrap(m.value[m.row], m.width)
  934. targetLineContent := grid[li.RowOffset-1]
  935. startCol := 0
  936. for i := 0; i < li.RowOffset-1; i++ {
  937. startCol += len(grid[i])
  938. }
  939. // Find position within the target wrapped line.
  940. offset := 0
  941. colInLine := 0
  942. for i, item := range targetLineContent {
  943. var itemWidth int
  944. switch v := item.(type) {
  945. case rune:
  946. itemWidth = rw.RuneWidth(v)
  947. case *attachment.Attachment:
  948. itemWidth = uniseg.StringWidth(v.Display)
  949. }
  950. if offset+itemWidth > charOffset {
  951. // Decide whether to stick with the previous index or move to the current
  952. // one based on which is closer to the target offset.
  953. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  954. colInLine = i + 1
  955. } else {
  956. colInLine = i
  957. }
  958. goto foundSameLine
  959. }
  960. offset += itemWidth
  961. }
  962. colInLine = len(targetLineContent)
  963. foundSameLine:
  964. m.col = startCol + colInLine
  965. }
  966. m.SetCursorColumn(m.col)
  967. }
  968. // SetCursorColumn moves the cursor to the given position. If the position is
  969. // out of bounds the cursor will be moved to the start or end accordingly.
  970. func (m *Model) SetCursorColumn(col int) {
  971. m.col = clamp(col, 0, len(m.value[m.row]))
  972. // Any time that we move the cursor horizontally we need to reset the last
  973. // offset so that the horizontal position when navigating is adjusted.
  974. m.lastCharOffset = 0
  975. }
  976. // CursorStart moves the cursor to the start of the input field.
  977. func (m *Model) CursorStart() {
  978. m.SetCursorColumn(0)
  979. }
  980. // CursorEnd moves the cursor to the end of the input field.
  981. func (m *Model) CursorEnd() {
  982. m.SetCursorColumn(len(m.value[m.row]))
  983. }
  984. func (m *Model) IsCursorAtEnd() bool {
  985. return m.CursorColumn() == len(m.value[m.row])
  986. }
  987. // Focused returns the focus state on the model.
  988. func (m Model) Focused() bool {
  989. return m.focus
  990. }
  991. // activeStyle returns the appropriate set of styles to use depending on
  992. // whether the textarea is focused or blurred.
  993. func (m Model) activeStyle() *StyleState {
  994. if m.focus {
  995. return &m.Styles.Focused
  996. }
  997. return &m.Styles.Blurred
  998. }
  999. // Focus sets the focus state on the model. When the model is in focus it can
  1000. // receive keyboard input and the cursor will be hidden.
  1001. func (m *Model) Focus() tea.Cmd {
  1002. m.focus = true
  1003. return m.virtualCursor.Focus()
  1004. }
  1005. // Blur removes the focus state on the model. When the model is blurred it can
  1006. // not receive keyboard input and the cursor will be hidden.
  1007. func (m *Model) Blur() {
  1008. m.focus = false
  1009. m.virtualCursor.Blur()
  1010. }
  1011. // Reset sets the input to its default state with no input.
  1012. func (m *Model) Reset() {
  1013. m.value = make([][]any, minHeight, maxLines)
  1014. m.col = 0
  1015. m.row = 0
  1016. m.SetCursorColumn(0)
  1017. }
  1018. // san initializes or retrieves the rune sanitizer.
  1019. func (m *Model) san() Sanitizer {
  1020. if m.rsan == nil {
  1021. // Textinput has all its input on a single line so collapse
  1022. // newlines/tabs to single spaces.
  1023. m.rsan = NewSanitizer()
  1024. }
  1025. return m.rsan
  1026. }
  1027. // deleteBeforeCursor deletes all text before the cursor. Returns whether or
  1028. // not the cursor blink should be reset.
  1029. func (m *Model) deleteBeforeCursor() {
  1030. m.value[m.row] = m.value[m.row][m.col:]
  1031. m.SetCursorColumn(0)
  1032. }
  1033. // deleteAfterCursor deletes all text after the cursor. Returns whether or not
  1034. // the cursor blink should be reset. If input is masked delete everything after
  1035. // the cursor so as not to reveal word breaks in the masked input.
  1036. func (m *Model) deleteAfterCursor() {
  1037. m.value[m.row] = m.value[m.row][:m.col]
  1038. m.SetCursorColumn(len(m.value[m.row]))
  1039. }
  1040. // transposeLeft exchanges the runes at the cursor and immediately
  1041. // before. No-op if the cursor is at the beginning of the line. If
  1042. // the cursor is not at the end of the line yet, moves the cursor to
  1043. // the right.
  1044. func (m *Model) transposeLeft() {
  1045. if m.col == 0 || len(m.value[m.row]) < 2 {
  1046. return
  1047. }
  1048. if m.col >= len(m.value[m.row]) {
  1049. m.SetCursorColumn(m.col - 1)
  1050. }
  1051. m.value[m.row][m.col-1], m.value[m.row][m.col] = m.value[m.row][m.col], m.value[m.row][m.col-1]
  1052. if m.col < len(m.value[m.row]) {
  1053. m.SetCursorColumn(m.col + 1)
  1054. }
  1055. }
  1056. // deleteWordLeft deletes the word left to the cursor. Returns whether or not
  1057. // the cursor blink should be reset.
  1058. func (m *Model) deleteWordLeft() {
  1059. if m.col == 0 || len(m.value[m.row]) == 0 {
  1060. return
  1061. }
  1062. // Linter note: it's critical that we acquire the initial cursor position
  1063. // here prior to altering it via SetCursor() below. As such, moving this
  1064. // call into the corresponding if clause does not apply here.
  1065. oldCol := m.col //nolint:ifshort
  1066. m.SetCursorColumn(m.col - 1)
  1067. for isSpaceAt(m.value[m.row], m.col) {
  1068. if m.col <= 0 {
  1069. break
  1070. }
  1071. // ignore series of whitespace before cursor
  1072. m.SetCursorColumn(m.col - 1)
  1073. }
  1074. for m.col > 0 {
  1075. if !isSpaceAt(m.value[m.row], m.col) {
  1076. m.SetCursorColumn(m.col - 1)
  1077. } else {
  1078. if m.col > 0 {
  1079. // keep the previous space
  1080. m.SetCursorColumn(m.col + 1)
  1081. }
  1082. break
  1083. }
  1084. }
  1085. if oldCol > len(m.value[m.row]) {
  1086. m.value[m.row] = m.value[m.row][:m.col]
  1087. } else {
  1088. m.value[m.row] = append(m.value[m.row][:m.col], m.value[m.row][oldCol:]...)
  1089. }
  1090. }
  1091. // deleteWordRight deletes the word right to the cursor.
  1092. func (m *Model) deleteWordRight() {
  1093. if m.col >= len(m.value[m.row]) || len(m.value[m.row]) == 0 {
  1094. return
  1095. }
  1096. oldCol := m.col
  1097. for m.col < len(m.value[m.row]) && isSpaceAt(m.value[m.row], m.col) {
  1098. // ignore series of whitespace after cursor
  1099. m.SetCursorColumn(m.col + 1)
  1100. }
  1101. for m.col < len(m.value[m.row]) {
  1102. if !isSpaceAt(m.value[m.row], m.col) {
  1103. m.SetCursorColumn(m.col + 1)
  1104. } else {
  1105. break
  1106. }
  1107. }
  1108. if m.col > len(m.value[m.row]) {
  1109. m.value[m.row] = m.value[m.row][:oldCol]
  1110. } else {
  1111. m.value[m.row] = append(m.value[m.row][:oldCol], m.value[m.row][m.col:]...)
  1112. }
  1113. m.SetCursorColumn(oldCol)
  1114. }
  1115. // characterRight moves the cursor one character to the right.
  1116. func (m *Model) characterRight() {
  1117. if m.col < len(m.value[m.row]) {
  1118. m.SetCursorColumn(m.col + 1)
  1119. } else {
  1120. if m.row < len(m.value)-1 {
  1121. m.row++
  1122. m.CursorStart()
  1123. }
  1124. }
  1125. }
  1126. // characterLeft moves the cursor one character to the left.
  1127. // If insideLine is set, the cursor is moved to the last
  1128. // character in the previous line, instead of one past that.
  1129. func (m *Model) characterLeft(insideLine bool) {
  1130. if m.col == 0 && m.row != 0 {
  1131. m.row--
  1132. m.CursorEnd()
  1133. if !insideLine {
  1134. return
  1135. }
  1136. }
  1137. if m.col > 0 {
  1138. m.SetCursorColumn(m.col - 1)
  1139. }
  1140. }
  1141. // wordLeft moves the cursor one word to the left. Returns whether or not the
  1142. // cursor blink should be reset. If input is masked, move input to the start
  1143. // so as not to reveal word breaks in the masked input.
  1144. func (m *Model) wordLeft() {
  1145. for {
  1146. m.characterLeft(true /* insideLine */)
  1147. if m.col < len(m.value[m.row]) && !isSpaceAt(m.value[m.row], m.col) {
  1148. break
  1149. }
  1150. }
  1151. for m.col > 0 {
  1152. if isSpaceAt(m.value[m.row], m.col-1) {
  1153. break
  1154. }
  1155. m.SetCursorColumn(m.col - 1)
  1156. }
  1157. }
  1158. // wordRight moves the cursor one word to the right. Returns whether or not the
  1159. // cursor blink should be reset. If the input is masked, move input to the end
  1160. // so as not to reveal word breaks in the masked input.
  1161. func (m *Model) wordRight() {
  1162. m.doWordRight(func(int, int) { /* nothing */ })
  1163. }
  1164. func (m *Model) doWordRight(fn func(charIdx int, pos int)) {
  1165. // Skip spaces forward.
  1166. for m.col >= len(m.value[m.row]) || isSpaceAt(m.value[m.row], m.col) {
  1167. if m.row == len(m.value)-1 && m.col == len(m.value[m.row]) {
  1168. // End of text.
  1169. break
  1170. }
  1171. m.characterRight()
  1172. }
  1173. charIdx := 0
  1174. for m.col < len(m.value[m.row]) {
  1175. if isSpaceAt(m.value[m.row], m.col) {
  1176. break
  1177. }
  1178. fn(charIdx, m.col)
  1179. m.SetCursorColumn(m.col + 1)
  1180. charIdx++
  1181. }
  1182. }
  1183. // uppercaseRight changes the word to the right to uppercase.
  1184. func (m *Model) uppercaseRight() {
  1185. m.doWordRight(func(_ int, i int) {
  1186. if r, ok := m.value[m.row][i].(rune); ok {
  1187. m.value[m.row][i] = unicode.ToUpper(r)
  1188. }
  1189. })
  1190. }
  1191. // lowercaseRight changes the word to the right to lowercase.
  1192. func (m *Model) lowercaseRight() {
  1193. m.doWordRight(func(_ int, i int) {
  1194. if r, ok := m.value[m.row][i].(rune); ok {
  1195. m.value[m.row][i] = unicode.ToLower(r)
  1196. }
  1197. })
  1198. }
  1199. // capitalizeRight changes the word to the right to title case.
  1200. func (m *Model) capitalizeRight() {
  1201. m.doWordRight(func(charIdx int, i int) {
  1202. if charIdx == 0 {
  1203. if r, ok := m.value[m.row][i].(rune); ok {
  1204. m.value[m.row][i] = unicode.ToTitle(r)
  1205. }
  1206. }
  1207. })
  1208. }
  1209. // LineInfo returns the number of characters from the start of the
  1210. // (soft-wrapped) line and the (soft-wrapped) line width.
  1211. func (m Model) LineInfo() LineInfo {
  1212. grid := m.memoizedWrap(m.value[m.row], m.width)
  1213. // Find out which line we are currently on. This can be determined by the
  1214. // m.col and counting the number of runes that we need to skip.
  1215. var counter int
  1216. for i, line := range grid {
  1217. start := counter
  1218. end := counter + len(line)
  1219. if m.col >= start && m.col <= end {
  1220. // This is the wrapped line the cursor is on.
  1221. // Special case: if the cursor is at the end of a wrapped line,
  1222. // and there's another wrapped line after it, the cursor should
  1223. // be considered at the beginning of the next line.
  1224. if m.col == end && i < len(grid)-1 {
  1225. nextLine := grid[i+1]
  1226. return LineInfo{
  1227. CharOffset: 0,
  1228. ColumnOffset: 0,
  1229. Height: len(grid),
  1230. RowOffset: i + 1,
  1231. StartColumn: end,
  1232. Width: len(nextLine),
  1233. CharWidth: uniseg.StringWidth(interfacesToString(nextLine)),
  1234. }
  1235. }
  1236. return LineInfo{
  1237. CharOffset: uniseg.StringWidth(interfacesToString(line[:max(0, m.col-start)])),
  1238. ColumnOffset: m.col - start,
  1239. Height: len(grid),
  1240. RowOffset: i,
  1241. StartColumn: start,
  1242. Width: len(line),
  1243. CharWidth: uniseg.StringWidth(interfacesToString(line)),
  1244. }
  1245. }
  1246. counter = end
  1247. }
  1248. return LineInfo{}
  1249. }
  1250. // Width returns the width of the textarea.
  1251. func (m Model) Width() int {
  1252. return m.width
  1253. }
  1254. // MoveToBegin moves the cursor to the beginning of the input.
  1255. func (m *Model) MoveToBegin() {
  1256. m.row = 0
  1257. m.SetCursorColumn(0)
  1258. }
  1259. // MoveToEnd moves the cursor to the end of the input.
  1260. func (m *Model) MoveToEnd() {
  1261. m.row = len(m.value) - 1
  1262. m.SetCursorColumn(len(m.value[m.row]))
  1263. }
  1264. // SetWidth sets the width of the textarea to fit exactly within the given width.
  1265. // This means that the textarea will account for the width of the prompt and
  1266. // whether or not line numbers are being shown.
  1267. //
  1268. // Ensure that SetWidth is called after setting the Prompt and ShowLineNumbers,
  1269. // It is important that the width of the textarea be exactly the given width
  1270. // and no more.
  1271. func (m *Model) SetWidth(w int) {
  1272. // Update prompt width only if there is no prompt function as
  1273. // [SetPromptFunc] updates the prompt width when it is called.
  1274. if m.promptFunc == nil {
  1275. // XXX: Do we even need this or can we calculate the prompt width
  1276. // at render time?
  1277. m.promptWidth = uniseg.StringWidth(m.Prompt)
  1278. }
  1279. // Add base style borders and padding to reserved outer width.
  1280. reservedOuter := m.activeStyle().Base.GetHorizontalFrameSize()
  1281. // Add prompt width to reserved inner width.
  1282. reservedInner := m.promptWidth
  1283. // Add line number width to reserved inner width.
  1284. if m.ShowLineNumbers {
  1285. // XXX: this was originally documented as needing "1 cell" but was,
  1286. // in practice, effectively hardcoded to 2 cells. We can, and should,
  1287. // reduce this to one gap and update the tests accordingly.
  1288. const gap = 2
  1289. // Number of digits plus 1 cell for the margin.
  1290. reservedInner += numDigits(m.MaxHeight) + gap
  1291. }
  1292. // Input width must be at least one more than the reserved inner and outer
  1293. // width. This gives us a minimum input width of 1.
  1294. minWidth := reservedInner + reservedOuter + 1
  1295. inputWidth := max(w, minWidth)
  1296. // Input width must be no more than maximum width.
  1297. if m.MaxWidth > 0 {
  1298. inputWidth = min(inputWidth, m.MaxWidth)
  1299. }
  1300. // Since the width of the viewport and input area is dependent on the width of
  1301. // borders, prompt and line numbers, we need to calculate it by subtracting
  1302. // the reserved width from them.
  1303. m.width = inputWidth - reservedOuter - reservedInner
  1304. }
  1305. // SetPromptFunc supersedes the Prompt field and sets a dynamic prompt instead.
  1306. //
  1307. // If the function returns a prompt that is shorter than the specified
  1308. // promptWidth, it will be padded to the left. If it returns a prompt that is
  1309. // longer, display artifacts may occur; the caller is responsible for computing
  1310. // an adequate promptWidth.
  1311. func (m *Model) SetPromptFunc(promptWidth int, fn func(lineIndex int) string) {
  1312. m.promptFunc = fn
  1313. m.promptWidth = promptWidth
  1314. }
  1315. // Height returns the current height of the textarea.
  1316. func (m Model) Height() int {
  1317. return m.height
  1318. }
  1319. // ContentHeight returns the actual height needed to display all content
  1320. // including wrapped lines.
  1321. func (m Model) ContentHeight() int {
  1322. totalLines := 0
  1323. for _, line := range m.value {
  1324. wrappedLines := m.memoizedWrap(line, m.width)
  1325. totalLines += len(wrappedLines)
  1326. }
  1327. // Ensure at least one line is shown
  1328. if totalLines == 0 {
  1329. totalLines = 1
  1330. }
  1331. return totalLines
  1332. }
  1333. // SetHeight sets the height of the textarea.
  1334. func (m *Model) SetHeight(h int) {
  1335. // Calculate the actual content height
  1336. contentHeight := m.ContentHeight()
  1337. // Use the content height as the actual height
  1338. if m.MaxHeight > 0 {
  1339. m.height = clamp(contentHeight, minHeight, m.MaxHeight)
  1340. } else {
  1341. m.height = max(contentHeight, minHeight)
  1342. }
  1343. }
  1344. // Update is the Bubble Tea update loop.
  1345. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
  1346. if !m.focus {
  1347. m.virtualCursor.Blur()
  1348. return m, nil
  1349. }
  1350. // Used to determine if the cursor should blink.
  1351. oldRow, oldCol := m.cursorLineNumber(), m.col
  1352. var cmds []tea.Cmd
  1353. if m.row >= len(m.value) {
  1354. m.value = append(m.value, make([]any, 0))
  1355. }
  1356. if m.value[m.row] == nil {
  1357. m.value[m.row] = make([]any, 0)
  1358. }
  1359. if m.MaxHeight > 0 && m.MaxHeight != m.cache.Capacity() {
  1360. m.cache = NewMemoCache[line, [][]any](m.MaxHeight)
  1361. }
  1362. switch msg := msg.(type) {
  1363. case tea.KeyPressMsg:
  1364. switch {
  1365. case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
  1366. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1367. if m.col >= len(m.value[m.row]) {
  1368. m.mergeLineBelow(m.row)
  1369. break
  1370. }
  1371. m.deleteAfterCursor()
  1372. case key.Matches(msg, m.KeyMap.DeleteBeforeCursor):
  1373. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1374. if m.col <= 0 {
  1375. m.mergeLineAbove(m.row)
  1376. break
  1377. }
  1378. m.deleteBeforeCursor()
  1379. case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
  1380. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1381. if m.col <= 0 {
  1382. m.mergeLineAbove(m.row)
  1383. break
  1384. }
  1385. if len(m.value[m.row]) > 0 && m.col > 0 {
  1386. m.value[m.row] = slices.Delete(m.value[m.row], m.col-1, m.col)
  1387. m.SetCursorColumn(m.col - 1)
  1388. }
  1389. case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
  1390. if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
  1391. m.value[m.row] = slices.Delete(m.value[m.row], m.col, m.col+1)
  1392. }
  1393. if m.col >= len(m.value[m.row]) {
  1394. m.mergeLineBelow(m.row)
  1395. break
  1396. }
  1397. case key.Matches(msg, m.KeyMap.DeleteWordBackward):
  1398. if m.col <= 0 {
  1399. m.mergeLineAbove(m.row)
  1400. break
  1401. }
  1402. m.deleteWordLeft()
  1403. case key.Matches(msg, m.KeyMap.DeleteWordForward):
  1404. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1405. if m.col >= len(m.value[m.row]) {
  1406. m.mergeLineBelow(m.row)
  1407. break
  1408. }
  1409. m.deleteWordRight()
  1410. case key.Matches(msg, m.KeyMap.InsertNewline):
  1411. m.Newline()
  1412. case key.Matches(msg, m.KeyMap.LineEnd):
  1413. m.CursorEnd()
  1414. case key.Matches(msg, m.KeyMap.LineStart):
  1415. m.CursorStart()
  1416. case key.Matches(msg, m.KeyMap.CharacterForward):
  1417. m.characterRight()
  1418. case key.Matches(msg, m.KeyMap.LineNext):
  1419. m.CursorDown()
  1420. case key.Matches(msg, m.KeyMap.WordForward):
  1421. m.wordRight()
  1422. case key.Matches(msg, m.KeyMap.CharacterBackward):
  1423. m.characterLeft(false /* insideLine */)
  1424. case key.Matches(msg, m.KeyMap.LinePrevious):
  1425. m.CursorUp()
  1426. case key.Matches(msg, m.KeyMap.WordBackward):
  1427. m.wordLeft()
  1428. case key.Matches(msg, m.KeyMap.InputBegin):
  1429. m.MoveToBegin()
  1430. case key.Matches(msg, m.KeyMap.InputEnd):
  1431. m.MoveToEnd()
  1432. case key.Matches(msg, m.KeyMap.LowercaseWordForward):
  1433. m.lowercaseRight()
  1434. case key.Matches(msg, m.KeyMap.UppercaseWordForward):
  1435. m.uppercaseRight()
  1436. case key.Matches(msg, m.KeyMap.CapitalizeWordForward):
  1437. m.capitalizeRight()
  1438. case key.Matches(msg, m.KeyMap.TransposeCharacterBackward):
  1439. m.transposeLeft()
  1440. default:
  1441. m.InsertRunesFromUserInput([]rune(msg.Text))
  1442. }
  1443. case pasteMsg:
  1444. m.InsertRunesFromUserInput([]rune(msg))
  1445. case pasteErrMsg:
  1446. m.Err = msg
  1447. }
  1448. var cmd tea.Cmd
  1449. newRow, newCol := m.cursorLineNumber(), m.col
  1450. m.virtualCursor, cmd = m.virtualCursor.Update(msg)
  1451. if (newRow != oldRow || newCol != oldCol) && m.virtualCursor.Mode() == cursor.CursorBlink {
  1452. m.virtualCursor.Blink = false
  1453. cmd = m.virtualCursor.BlinkCmd()
  1454. }
  1455. cmds = append(cmds, cmd)
  1456. return m, tea.Batch(cmds...)
  1457. }
  1458. // View renders the text area in its current state.
  1459. func (m Model) View() string {
  1460. m.updateVirtualCursorStyle()
  1461. if m.Value() == "" && m.row == 0 && m.col == 0 && m.Placeholder != "" {
  1462. return m.placeholderView()
  1463. }
  1464. m.virtualCursor.TextStyle = m.activeStyle().computedCursorLine()
  1465. var (
  1466. s strings.Builder
  1467. style lipgloss.Style
  1468. newLines int
  1469. widestLineNumber int
  1470. lineInfo = m.LineInfo()
  1471. styles = m.activeStyle()
  1472. )
  1473. displayLine := 0
  1474. for l, line := range m.value {
  1475. wrappedLines := m.memoizedWrap(line, m.width)
  1476. if m.row == l {
  1477. style = styles.computedCursorLine()
  1478. } else {
  1479. style = styles.computedText()
  1480. }
  1481. for wl, wrappedLine := range wrappedLines {
  1482. prompt := m.promptView(displayLine)
  1483. prompt = styles.computedPrompt().Render(prompt)
  1484. s.WriteString(style.Render(prompt))
  1485. displayLine++
  1486. var ln string
  1487. if m.ShowLineNumbers {
  1488. if wl == 0 { // normal line
  1489. isCursorLine := m.row == l
  1490. s.WriteString(m.lineNumberView(l+1, isCursorLine))
  1491. } else { // soft wrapped line
  1492. isCursorLine := m.row == l
  1493. s.WriteString(m.lineNumberView(-1, isCursorLine))
  1494. }
  1495. }
  1496. // Note the widest line number for padding purposes later.
  1497. lnw := uniseg.StringWidth(ln)
  1498. if lnw > widestLineNumber {
  1499. widestLineNumber = lnw
  1500. }
  1501. wrappedLineStr := interfacesToString(wrappedLine)
  1502. strwidth := uniseg.StringWidth(wrappedLineStr)
  1503. padding := m.width - strwidth
  1504. // If the trailing space causes the line to be wider than the
  1505. // width, we should not draw it to the screen since it will result
  1506. // in an extra space at the end of the line which can look off when
  1507. // the cursor line is showing.
  1508. if strwidth > m.width {
  1509. // The character causing the line to be wider than the width is
  1510. // guaranteed to be a space since any other character would
  1511. // have been wrapped.
  1512. wrappedLineStr = strings.TrimSuffix(wrappedLineStr, " ")
  1513. padding = m.width - uniseg.StringWidth(wrappedLineStr)
  1514. }
  1515. if m.row == l && lineInfo.RowOffset == wl {
  1516. // Render the part of the line before the cursor
  1517. s.WriteString(
  1518. m.renderLineWithAttachments(
  1519. wrappedLine[:lineInfo.ColumnOffset],
  1520. style,
  1521. ),
  1522. )
  1523. if m.col >= len(line) && lineInfo.CharOffset >= m.width {
  1524. m.virtualCursor.SetChar(" ")
  1525. s.WriteString(m.virtualCursor.View())
  1526. } else if lineInfo.ColumnOffset < len(wrappedLine) {
  1527. // Render the item under the cursor
  1528. item := wrappedLine[lineInfo.ColumnOffset]
  1529. if att, ok := item.(*attachment.Attachment); ok {
  1530. // Item at cursor is an attachment. Render it with the selection style.
  1531. // This becomes the "cursor" visually.
  1532. s.WriteString(m.Styles.SelectedAttachment.Render(att.Display))
  1533. } else {
  1534. // Item at cursor is a rune. Render it with the virtual cursor.
  1535. m.virtualCursor.SetChar(string(item.(rune)))
  1536. s.WriteString(style.Render(m.virtualCursor.View()))
  1537. }
  1538. // Render the part of the line after the cursor
  1539. s.WriteString(m.renderLineWithAttachments(wrappedLine[lineInfo.ColumnOffset+1:], style))
  1540. } else {
  1541. // Cursor is at the end of the line
  1542. m.virtualCursor.SetChar(" ")
  1543. s.WriteString(style.Render(m.virtualCursor.View()))
  1544. }
  1545. } else {
  1546. s.WriteString(m.renderLineWithAttachments(wrappedLine, style))
  1547. }
  1548. s.WriteString(style.Render(strings.Repeat(" ", max(0, padding))))
  1549. s.WriteRune('\n')
  1550. newLines++
  1551. }
  1552. }
  1553. // Remove the trailing newline from the last line
  1554. result := s.String()
  1555. if len(result) > 0 && result[len(result)-1] == '\n' {
  1556. result = result[:len(result)-1]
  1557. }
  1558. return styles.Base.Render(result)
  1559. }
  1560. // promptView renders a single line of the prompt.
  1561. func (m Model) promptView(displayLine int) (prompt string) {
  1562. prompt = m.Prompt
  1563. if m.promptFunc == nil {
  1564. return prompt
  1565. }
  1566. prompt = m.promptFunc(displayLine)
  1567. width := lipgloss.Width(prompt)
  1568. if width < m.promptWidth {
  1569. prompt = fmt.Sprintf("%*s%s", m.promptWidth-width, "", prompt)
  1570. }
  1571. return m.activeStyle().computedPrompt().Render(prompt)
  1572. }
  1573. // lineNumberView renders the line number.
  1574. //
  1575. // If the argument is less than 0, a space styled as a line number is returned
  1576. // instead. Such cases are used for soft-wrapped lines.
  1577. //
  1578. // The second argument indicates whether this line number is for a 'cursorline'
  1579. // line number.
  1580. func (m Model) lineNumberView(n int, isCursorLine bool) (str string) {
  1581. if !m.ShowLineNumbers {
  1582. return ""
  1583. }
  1584. if n <= 0 {
  1585. str = " "
  1586. } else {
  1587. str = strconv.Itoa(n)
  1588. }
  1589. // XXX: is textStyle really necessary here?
  1590. textStyle := m.activeStyle().computedText()
  1591. lineNumberStyle := m.activeStyle().computedLineNumber()
  1592. if isCursorLine {
  1593. textStyle = m.activeStyle().computedCursorLine()
  1594. lineNumberStyle = m.activeStyle().computedCursorLineNumber()
  1595. }
  1596. // Format line number dynamically based on the maximum number of lines.
  1597. digits := len(strconv.Itoa(m.MaxHeight))
  1598. str = fmt.Sprintf(" %*v ", digits, str)
  1599. return textStyle.Render(lineNumberStyle.Render(str))
  1600. }
  1601. // placeholderView returns the prompt and placeholder, if any.
  1602. func (m Model) placeholderView() string {
  1603. var (
  1604. s strings.Builder
  1605. p = m.Placeholder
  1606. styles = m.activeStyle()
  1607. )
  1608. // word wrap lines
  1609. pwordwrap := ansi.Wordwrap(p, m.width, "")
  1610. // hard wrap lines (handles lines that could not be word wrapped)
  1611. pwrap := ansi.Hardwrap(pwordwrap, m.width, true)
  1612. // split string by new lines
  1613. plines := strings.Split(strings.TrimSpace(pwrap), "\n")
  1614. // Only render the actual placeholder lines, not padded to m.height
  1615. maxLines := max(len(plines), 1) // At least show one line for cursor
  1616. for i := range maxLines {
  1617. isLineNumber := len(plines) > i
  1618. lineStyle := styles.computedPlaceholder()
  1619. if len(plines) > i {
  1620. lineStyle = styles.computedCursorLine()
  1621. }
  1622. // render prompt
  1623. prompt := m.promptView(i)
  1624. prompt = styles.computedPrompt().Render(prompt)
  1625. s.WriteString(lineStyle.Render(prompt))
  1626. // when show line numbers enabled:
  1627. // - render line number for only the cursor line
  1628. // - indent other placeholder lines
  1629. // this is consistent with vim with line numbers enabled
  1630. if m.ShowLineNumbers {
  1631. var ln int
  1632. switch {
  1633. case i == 0:
  1634. ln = i + 1
  1635. fallthrough
  1636. case len(plines) > i:
  1637. s.WriteString(m.lineNumberView(ln, isLineNumber))
  1638. default:
  1639. }
  1640. }
  1641. switch {
  1642. // first line
  1643. case i == 0:
  1644. // first character of first line as cursor with character
  1645. m.virtualCursor.TextStyle = styles.computedPlaceholder()
  1646. m.virtualCursor.SetChar(string(plines[0][0]))
  1647. s.WriteString(lineStyle.Render(m.virtualCursor.View()))
  1648. // the rest of the first line
  1649. placeholderTail := plines[0][1:]
  1650. gap := strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[0])))
  1651. renderedPlaceholder := styles.computedPlaceholder().Render(placeholderTail + gap)
  1652. s.WriteString(lineStyle.Render(renderedPlaceholder))
  1653. // remaining lines
  1654. case len(plines) > i:
  1655. // current line placeholder text
  1656. if len(plines) > i {
  1657. placeholderLine := plines[i]
  1658. gap := strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[i])))
  1659. s.WriteString(lineStyle.Render(placeholderLine + gap))
  1660. }
  1661. default:
  1662. // end of line buffer character
  1663. eob := styles.computedEndOfBuffer().Render(string(m.EndOfBufferCharacter))
  1664. s.WriteString(eob)
  1665. }
  1666. // terminate with new line (except for last line)
  1667. if i < maxLines-1 {
  1668. s.WriteRune('\n')
  1669. }
  1670. }
  1671. return styles.Base.Render(s.String())
  1672. }
  1673. // Blink returns the blink command for the virtual cursor.
  1674. func Blink() tea.Msg {
  1675. return cursor.Blink()
  1676. }
  1677. // Cursor returns a [tea.Cursor] for rendering a real cursor in a Bubble Tea
  1678. // program. This requires that [Model.VirtualCursor] is set to false.
  1679. //
  1680. // Note that you will almost certainly also need to adjust the offset cursor
  1681. // position per the textarea's per the textarea's position in the terminal.
  1682. //
  1683. // Example:
  1684. //
  1685. // // In your top-level View function:
  1686. // f := tea.NewFrame(m.textarea.View())
  1687. // f.Cursor = m.textarea.Cursor()
  1688. // f.Cursor.Position.X += offsetX
  1689. // f.Cursor.Position.Y += offsetY
  1690. func (m Model) Cursor() *tea.Cursor {
  1691. if m.VirtualCursor {
  1692. return nil
  1693. }
  1694. lineInfo := m.LineInfo()
  1695. w := lipgloss.Width
  1696. baseStyle := m.activeStyle().Base
  1697. xOffset := lineInfo.CharOffset +
  1698. w(m.promptView(0)) +
  1699. w(m.lineNumberView(0, false)) +
  1700. baseStyle.GetMarginLeft() +
  1701. baseStyle.GetPaddingLeft() +
  1702. baseStyle.GetBorderLeftSize()
  1703. yOffset := m.cursorLineNumber() -
  1704. baseStyle.GetMarginTop() +
  1705. baseStyle.GetPaddingTop() +
  1706. baseStyle.GetBorderTopSize()
  1707. c := tea.NewCursor(xOffset, yOffset)
  1708. c.Blink = m.Styles.Cursor.Blink
  1709. c.Color = m.Styles.Cursor.Color
  1710. c.Shape = m.Styles.Cursor.Shape
  1711. return c
  1712. }
  1713. func (m Model) memoizedWrap(content []any, width int) [][]any {
  1714. input := line{content: content, width: width}
  1715. if v, ok := m.cache.Get(input); ok {
  1716. return v
  1717. }
  1718. v := wrapInterfaces(content, width)
  1719. m.cache.Set(input, v)
  1720. return v
  1721. }
  1722. // cursorLineNumber returns the line number that the cursor is on.
  1723. // This accounts for soft wrapped lines.
  1724. func (m Model) cursorLineNumber() int {
  1725. line := 0
  1726. for i := range m.row {
  1727. // Calculate the number of lines that the current line will be split
  1728. // into.
  1729. line += len(m.memoizedWrap(m.value[i], m.width))
  1730. }
  1731. line += m.LineInfo().RowOffset
  1732. return line
  1733. }
  1734. // mergeLineBelow merges the current line the cursor is on with the line below.
  1735. func (m *Model) mergeLineBelow(row int) {
  1736. if row >= len(m.value)-1 {
  1737. return
  1738. }
  1739. // To perform a merge, we will need to combine the two lines and then
  1740. m.value[row] = append(m.value[row], m.value[row+1]...)
  1741. // Shift all lines up by one
  1742. for i := row + 1; i < len(m.value)-1; i++ {
  1743. m.value[i] = m.value[i+1]
  1744. }
  1745. // And, remove the last line
  1746. if len(m.value) > 0 {
  1747. m.value = m.value[:len(m.value)-1]
  1748. }
  1749. }
  1750. // mergeLineAbove merges the current line the cursor is on with the line above.
  1751. func (m *Model) mergeLineAbove(row int) {
  1752. if row <= 0 {
  1753. return
  1754. }
  1755. m.col = len(m.value[row-1])
  1756. m.row = m.row - 1
  1757. // To perform a merge, we will need to combine the two lines and then
  1758. m.value[row-1] = append(m.value[row-1], m.value[row]...)
  1759. // Shift all lines up by one
  1760. for i := row; i < len(m.value)-1; i++ {
  1761. m.value[i] = m.value[i+1]
  1762. }
  1763. // And, remove the last line
  1764. if len(m.value) > 0 {
  1765. m.value = m.value[:len(m.value)-1]
  1766. }
  1767. }
  1768. func (m *Model) splitLine(row, col int) {
  1769. // To perform a split, take the current line and keep the content before
  1770. // the cursor, take the content after the cursor and make it the content of
  1771. // the line underneath, and shift the remaining lines down by one
  1772. head, tailSrc := m.value[row][:col], m.value[row][col:]
  1773. tail := copyInterfaceSlice(tailSrc)
  1774. m.value = append(m.value[:row+1], m.value[row:]...)
  1775. m.value[row] = head
  1776. m.value[row+1] = tail
  1777. m.col = 0
  1778. m.row++
  1779. }
  1780. func itemWidth(item any) int {
  1781. switch v := item.(type) {
  1782. case rune:
  1783. return rw.RuneWidth(v)
  1784. case *attachment.Attachment:
  1785. return uniseg.StringWidth(v.Display)
  1786. }
  1787. return 0
  1788. }
  1789. // forceWrapAttachment splits an attachment's display text across multiple lines
  1790. func forceWrapAttachment(att *attachment.Attachment, width int) [][]any {
  1791. if width <= 0 {
  1792. return [][]any{{att}}
  1793. }
  1794. display := att.Display
  1795. displayRunes := []rune(display)
  1796. if len(displayRunes) <= width {
  1797. return [][]any{{att}}
  1798. }
  1799. var lines [][]any
  1800. start := 0
  1801. for start < len(displayRunes) {
  1802. // Calculate how many runes fit in this line
  1803. end := start + width
  1804. if end > len(displayRunes) {
  1805. end = len(displayRunes)
  1806. }
  1807. // Create a wrapped attachment for this segment
  1808. wrappedAtt := &attachment.Attachment{
  1809. ID: att.ID,
  1810. Type: att.Type,
  1811. Display: string(displayRunes[start:end]),
  1812. URL: att.URL,
  1813. Filename: att.Filename,
  1814. MediaType: att.MediaType,
  1815. Source: att.Source,
  1816. }
  1817. lines = append(lines, []any{wrappedAtt})
  1818. start = end
  1819. }
  1820. return lines
  1821. }
  1822. // forceWrapWord splits a word that's too long to fit within the given width
  1823. func forceWrapWord(word []any, width int) [][]any {
  1824. if width <= 0 || len(word) == 0 {
  1825. return [][]any{word}
  1826. }
  1827. var lines [][]any
  1828. currentLine := []any{}
  1829. currentWidth := 0
  1830. for _, item := range word {
  1831. if att, ok := item.(*attachment.Attachment); ok {
  1832. // Handle attachment that might be too wide
  1833. attWidth := uniseg.StringWidth(att.Display)
  1834. // If the attachment display is too wide, split it
  1835. if attWidth > width {
  1836. // Finish current line if it has content
  1837. if len(currentLine) > 0 {
  1838. lines = append(lines, currentLine)
  1839. currentLine = []any{}
  1840. currentWidth = 0
  1841. }
  1842. // Split the attachment display across multiple lines
  1843. wrappedAttachment := forceWrapAttachment(att, width)
  1844. lines = append(lines, wrappedAttachment...)
  1845. continue
  1846. }
  1847. // If adding this attachment would exceed the width, start a new line
  1848. if currentWidth+attWidth > width && len(currentLine) > 0 {
  1849. lines = append(lines, currentLine)
  1850. currentLine = []any{}
  1851. currentWidth = 0
  1852. }
  1853. currentLine = append(currentLine, item)
  1854. currentWidth += attWidth
  1855. } else if r, ok := item.(rune); ok {
  1856. itemWidth := rw.RuneWidth(r)
  1857. // If adding this rune would exceed the width, start a new line
  1858. if currentWidth+itemWidth > width && len(currentLine) > 0 {
  1859. lines = append(lines, currentLine)
  1860. currentLine = []any{}
  1861. currentWidth = 0
  1862. }
  1863. currentLine = append(currentLine, item)
  1864. currentWidth += itemWidth
  1865. }
  1866. }
  1867. // Add the last line if it has content
  1868. if len(currentLine) > 0 {
  1869. lines = append(lines, currentLine)
  1870. }
  1871. return lines
  1872. }
  1873. func wrapInterfaces(content []any, width int) [][]any {
  1874. if width <= 0 {
  1875. return [][]any{content}
  1876. }
  1877. var (
  1878. lines = [][]any{{}}
  1879. word = []any{}
  1880. wordW int
  1881. lineW int
  1882. spaceW int
  1883. inSpaces bool
  1884. )
  1885. for _, item := range content {
  1886. itemW := 0
  1887. isSpace := false
  1888. if r, ok := item.(rune); ok {
  1889. if unicode.IsSpace(r) {
  1890. isSpace = true
  1891. }
  1892. itemW = rw.RuneWidth(r)
  1893. } else if att, ok := item.(*attachment.Attachment); ok {
  1894. itemW = uniseg.StringWidth(att.Display)
  1895. }
  1896. if isSpace {
  1897. if !inSpaces {
  1898. // End of a word
  1899. if lineW > 0 && lineW+wordW > width {
  1900. // If the word itself is too long to fit on a line, force-wrap it
  1901. if wordW > width {
  1902. wrappedLines := forceWrapWord(word, width)
  1903. lines = append(lines, wrappedLines...)
  1904. // Calculate width of the last wrapped line
  1905. lastLine := wrappedLines[len(wrappedLines)-1]
  1906. lineW = 0
  1907. for _, item := range lastLine {
  1908. if r, ok := item.(rune); ok {
  1909. lineW += rw.RuneWidth(r)
  1910. } else if att, ok := item.(*attachment.Attachment); ok {
  1911. lineW += uniseg.StringWidth(att.Display)
  1912. }
  1913. }
  1914. } else {
  1915. lines = append(lines, word)
  1916. lineW = wordW
  1917. }
  1918. } else {
  1919. // Check if the word needs to be force-wrapped even when it fits on the current line
  1920. if wordW > width {
  1921. currentLine := lines[len(lines)-1]
  1922. wrappedWord := forceWrapWord(word, width-lineW)
  1923. if len(wrappedWord) > 0 {
  1924. lines[len(lines)-1] = append(currentLine, wrappedWord[0]...)
  1925. for i := 1; i < len(wrappedWord); i++ {
  1926. lines = append(lines, wrappedWord[i])
  1927. }
  1928. // Calculate width of the last wrapped line
  1929. lastLine := wrappedWord[len(wrappedWord)-1]
  1930. lineW = 0
  1931. for _, item := range lastLine {
  1932. if r, ok := item.(rune); ok {
  1933. lineW += rw.RuneWidth(r)
  1934. } else if att, ok := item.(*attachment.Attachment); ok {
  1935. lineW += uniseg.StringWidth(att.Display)
  1936. }
  1937. }
  1938. }
  1939. } else {
  1940. lines[len(lines)-1] = append(lines[len(lines)-1], word...)
  1941. lineW += wordW
  1942. }
  1943. }
  1944. word = nil
  1945. wordW = 0
  1946. }
  1947. inSpaces = true
  1948. spaceW += itemW
  1949. } else { // It's not a space, it's a character for a word.
  1950. if inSpaces {
  1951. // We just finished a block of spaces. Handle them now.
  1952. lineW += spaceW
  1953. for i := 0; i < spaceW; i++ {
  1954. lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
  1955. }
  1956. if lineW > width {
  1957. // The spaces made the line overflow. Start a new line for the upcoming word.
  1958. lines = append(lines, []any{})
  1959. lineW = 0
  1960. }
  1961. spaceW = 0
  1962. }
  1963. inSpaces = false
  1964. word = append(word, item)
  1965. wordW += itemW
  1966. }
  1967. }
  1968. // Handle any remaining word/spaces at the end of the content.
  1969. if wordW > 0 {
  1970. if lineW > 0 && lineW+wordW > width {
  1971. // If the word itself is too long to fit on a line, force-wrap it
  1972. if wordW > width {
  1973. wrappedLines := forceWrapWord(word, width)
  1974. lines = append(lines, wrappedLines...)
  1975. // Calculate width of the last wrapped line
  1976. lastLine := wrappedLines[len(wrappedLines)-1]
  1977. lineW = 0
  1978. for _, item := range lastLine {
  1979. if r, ok := item.(rune); ok {
  1980. lineW += rw.RuneWidth(r)
  1981. } else if att, ok := item.(*attachment.Attachment); ok {
  1982. lineW += uniseg.StringWidth(att.Display)
  1983. }
  1984. }
  1985. } else {
  1986. lines = append(lines, word)
  1987. lineW = wordW
  1988. }
  1989. } else {
  1990. // Check if the word needs to be force-wrapped even when it fits on the current line
  1991. if wordW > width {
  1992. currentLine := lines[len(lines)-1]
  1993. wrappedWord := forceWrapWord(word, width-lineW)
  1994. if len(wrappedWord) > 0 {
  1995. lines[len(lines)-1] = append(currentLine, wrappedWord[0]...)
  1996. for i := 1; i < len(wrappedWord); i++ {
  1997. lines = append(lines, wrappedWord[i])
  1998. }
  1999. // Calculate width of the last wrapped line
  2000. lastLine := wrappedWord[len(wrappedWord)-1]
  2001. lineW = 0
  2002. for _, item := range lastLine {
  2003. if r, ok := item.(rune); ok {
  2004. lineW += rw.RuneWidth(r)
  2005. } else if att, ok := item.(*attachment.Attachment); ok {
  2006. lineW += uniseg.StringWidth(att.Display)
  2007. }
  2008. }
  2009. }
  2010. } else {
  2011. lines[len(lines)-1] = append(lines[len(lines)-1], word...)
  2012. lineW += wordW
  2013. }
  2014. }
  2015. }
  2016. if spaceW > 0 {
  2017. // There are trailing spaces. Add them.
  2018. for i := 0; i < spaceW; i++ {
  2019. lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
  2020. lineW += 1
  2021. }
  2022. if lineW > width {
  2023. lines = append(lines, []any{})
  2024. }
  2025. }
  2026. return lines
  2027. }
  2028. func repeatSpaces(n int) []rune {
  2029. return []rune(strings.Repeat(string(' '), n))
  2030. }
  2031. // numDigits returns the number of digits in an integer.
  2032. func numDigits(n int) int {
  2033. if n == 0 {
  2034. return 1
  2035. }
  2036. count := 0
  2037. num := abs(n)
  2038. for num > 0 {
  2039. count++
  2040. num /= 10
  2041. }
  2042. return count
  2043. }
  2044. func clamp(v, low, high int) int {
  2045. if high < low {
  2046. low, high = high, low
  2047. }
  2048. return min(high, max(low, v))
  2049. }
  2050. func abs(n int) int {
  2051. if n < 0 {
  2052. return -n
  2053. }
  2054. return n
  2055. }