textarea.go 58 KB

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