textarea.go 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377
  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. // removeAttachmentAtCursor replaces the attachment at or immediately before the
  582. // cursor with its textual display and positions the cursor at the end of the
  583. // inserted text. Returns true if an attachment was removed.
  584. func (m *Model) removeAttachmentAtCursor() bool {
  585. att, startIdx, _ := m.isAttachmentAtCursor()
  586. if att == nil {
  587. return false
  588. }
  589. // Replace the attachment element with the display runes
  590. before := m.value[m.row][:startIdx]
  591. after := m.value[m.row][startIdx+1:]
  592. replacement := runesToInterfaces([]rune(att.Display))
  593. newRow := make([]any, 0, len(before)+len(replacement)+len(after))
  594. newRow = append(newRow, before...)
  595. newRow = append(newRow, replacement...)
  596. newRow = append(newRow, after...)
  597. m.value[m.row] = newRow
  598. m.col = startIdx + len(replacement)
  599. m.SetCursorColumn(m.col)
  600. return true
  601. }
  602. // ReplaceRange replaces text from startCol to endCol on the current row with the given string.
  603. // This preserves attachments outside the replaced range.
  604. func (m *Model) ReplaceRange(startCol, endCol int, replacement string) {
  605. if m.row >= len(m.value) || startCol < 0 || endCol < startCol {
  606. return
  607. }
  608. // Ensure bounds are within the current row
  609. rowLen := len(m.value[m.row])
  610. startCol = max(0, min(startCol, rowLen))
  611. endCol = max(startCol, min(endCol, rowLen))
  612. // Create new row content: before + replacement + after
  613. before := m.value[m.row][:startCol]
  614. after := m.value[m.row][endCol:]
  615. replacementRunes := runesToInterfaces([]rune(replacement))
  616. // Combine the parts
  617. newRow := make([]any, 0, len(before)+len(replacementRunes)+len(after))
  618. newRow = append(newRow, before...)
  619. newRow = append(newRow, replacementRunes...)
  620. newRow = append(newRow, after...)
  621. m.value[m.row] = newRow
  622. // Position cursor at end of replacement
  623. m.col = startCol + len(replacementRunes)
  624. m.SetCursorColumn(m.col)
  625. }
  626. // CurrentRowLength returns the length of the current row.
  627. func (m *Model) CurrentRowLength() int {
  628. if m.row >= len(m.value) {
  629. return 0
  630. }
  631. return len(m.value[m.row])
  632. }
  633. // GetAttachments returns all attachments in the textarea with accurate position indices.
  634. func (m Model) GetAttachments() []*attachment.Attachment {
  635. var attachments []*attachment.Attachment
  636. position := 0 // Track absolute position in the text
  637. for rowIdx, row := range m.value {
  638. colPosition := 0 // Track position within the current row
  639. for _, item := range row {
  640. switch v := item.(type) {
  641. case *attachment.Attachment:
  642. // Clone the attachment to avoid modifying the original
  643. att := *v
  644. att.StartIndex = position + colPosition
  645. att.EndIndex = position + colPosition + len(v.Display)
  646. attachments = append(attachments, &att)
  647. colPosition += len(v.Display)
  648. case rune:
  649. colPosition++
  650. }
  651. }
  652. // Add newline character position (except for last row)
  653. if rowIdx < len(m.value)-1 {
  654. position += colPosition + 1 // +1 for newline
  655. } else {
  656. position += colPosition
  657. }
  658. }
  659. return attachments
  660. }
  661. // InsertRunesFromUserInput inserts runes at the current cursor position.
  662. func (m *Model) InsertRunesFromUserInput(runes []rune) {
  663. // Clean up any special characters in the input provided by the
  664. // clipboard. This avoids bugs due to e.g. tab characters and
  665. // whatnot.
  666. runes = m.san().Sanitize(runes)
  667. if m.CharLimit > 0 {
  668. availSpace := m.CharLimit - m.Length()
  669. // If the char limit's been reached, cancel.
  670. if availSpace <= 0 {
  671. return
  672. }
  673. // If there's not enough space to paste the whole thing cut the pasted
  674. // runes down so they'll fit.
  675. if availSpace < len(runes) {
  676. runes = runes[:availSpace]
  677. }
  678. }
  679. // Split the input into lines.
  680. var lines [][]rune
  681. lstart := 0
  682. for i := range runes {
  683. if runes[i] == '\n' {
  684. // Queue a line to become a new row in the text area below.
  685. // Beware to clamp the max capacity of the slice, to ensure no
  686. // data from different rows get overwritten when later edits
  687. // will modify this line.
  688. lines = append(lines, runes[lstart:i:i])
  689. lstart = i + 1
  690. }
  691. }
  692. if lstart <= len(runes) {
  693. // The last line did not end with a newline character.
  694. // Take it now.
  695. lines = append(lines, runes[lstart:])
  696. }
  697. // Obey the maximum line limit.
  698. if maxLines > 0 && len(m.value)+len(lines)-1 > maxLines {
  699. allowedHeight := max(0, maxLines-len(m.value)+1)
  700. lines = lines[:allowedHeight]
  701. }
  702. if len(lines) == 0 {
  703. // Nothing left to insert.
  704. return
  705. }
  706. // Save the remainder of the original line at the current
  707. // cursor position.
  708. tail := copyInterfaceSlice(m.value[m.row][m.col:])
  709. // Paste the first line at the current cursor position.
  710. m.value[m.row] = append(m.value[m.row][:m.col], runesToInterfaces(lines[0])...)
  711. m.col += len(lines[0])
  712. if numExtraLines := len(lines) - 1; numExtraLines > 0 {
  713. // Add the new lines.
  714. // We try to reuse the slice if there's already space.
  715. var newGrid [][]any
  716. if cap(m.value) >= len(m.value)+numExtraLines {
  717. // Can reuse the extra space.
  718. newGrid = m.value[:len(m.value)+numExtraLines]
  719. } else {
  720. // No space left; need a new slice.
  721. newGrid = make([][]any, len(m.value)+numExtraLines)
  722. copy(newGrid, m.value[:m.row+1])
  723. }
  724. // Add all the rows that were after the cursor in the original
  725. // grid at the end of the new grid.
  726. copy(newGrid[m.row+1+numExtraLines:], m.value[m.row+1:])
  727. m.value = newGrid
  728. // Insert all the new lines in the middle.
  729. for _, l := range lines[1:] {
  730. m.row++
  731. m.value[m.row] = runesToInterfaces(l)
  732. m.col = len(l)
  733. }
  734. }
  735. // Finally add the tail at the end of the last line inserted.
  736. m.value[m.row] = append(m.value[m.row], tail...)
  737. m.SetCursorColumn(m.col)
  738. }
  739. // Value returns the value of the text input.
  740. func (m Model) Value() string {
  741. if m.value == nil {
  742. return ""
  743. }
  744. var v strings.Builder
  745. for _, l := range m.value {
  746. for _, item := range l {
  747. switch val := item.(type) {
  748. case rune:
  749. v.WriteRune(val)
  750. case *attachment.Attachment:
  751. v.WriteString(val.Display)
  752. }
  753. }
  754. v.WriteByte('\n')
  755. }
  756. return strings.TrimSuffix(v.String(), "\n")
  757. }
  758. // Length returns the number of characters currently in the text input.
  759. func (m *Model) Length() int {
  760. var l int
  761. for _, row := range m.value {
  762. for _, item := range row {
  763. switch val := item.(type) {
  764. case rune:
  765. l += rw.RuneWidth(val)
  766. case *attachment.Attachment:
  767. l += uniseg.StringWidth(val.Display)
  768. }
  769. }
  770. }
  771. // We add len(m.value) to include the newline characters.
  772. return l + len(m.value) - 1
  773. }
  774. // LineCount returns the number of lines that are currently in the text input.
  775. func (m *Model) LineCount() int {
  776. return m.ContentHeight()
  777. }
  778. // Line returns the line position.
  779. func (m Model) Line() int {
  780. return m.row
  781. }
  782. // CursorColumn returns the cursor's column position (slice index).
  783. func (m Model) CursorColumn() int {
  784. return m.col
  785. }
  786. // LastRuneIndex returns the index of the last occurrence of a rune on the current line,
  787. // searching backwards from the current cursor position.
  788. // Returns -1 if the rune is not found before the cursor.
  789. func (m Model) LastRuneIndex(r rune) int {
  790. if m.row >= len(m.value) {
  791. return -1
  792. }
  793. // Iterate backwards from just before the cursor position
  794. for i := m.col - 1; i >= 0; i-- {
  795. if i < len(m.value[m.row]) {
  796. if item, ok := m.value[m.row][i].(rune); ok && item == r {
  797. return i
  798. }
  799. }
  800. }
  801. return -1
  802. }
  803. func (m *Model) Newline() {
  804. if m.MaxHeight > 0 && len(m.value) >= m.MaxHeight {
  805. return
  806. }
  807. m.col = clamp(m.col, 0, len(m.value[m.row]))
  808. m.splitLine(m.row, m.col)
  809. }
  810. // mapVisualOffsetToSliceIndex converts a visual column offset to a slice index.
  811. // This is used to maintain the cursor's horizontal position when moving vertically.
  812. func (m *Model) mapVisualOffsetToSliceIndex(row int, charOffset int) int {
  813. if row < 0 || row >= len(m.value) {
  814. return 0
  815. }
  816. offset := 0
  817. // Find the slice index that corresponds to the visual offset.
  818. for i, item := range m.value[row] {
  819. var itemWidth int
  820. switch v := item.(type) {
  821. case rune:
  822. itemWidth = rw.RuneWidth(v)
  823. case *attachment.Attachment:
  824. itemWidth = uniseg.StringWidth(v.Display)
  825. }
  826. // If the target offset falls within the current item, this is our index.
  827. if offset+itemWidth > charOffset {
  828. // Decide whether to stick with the previous index or move to the current
  829. // one based on which is closer to the target offset.
  830. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  831. return i + 1
  832. }
  833. return i
  834. }
  835. offset += itemWidth
  836. }
  837. return len(m.value[row])
  838. }
  839. // CursorDown moves the cursor down by one line.
  840. func (m *Model) CursorDown() {
  841. li := m.LineInfo()
  842. charOffset := max(m.lastCharOffset, li.CharOffset)
  843. m.lastCharOffset = charOffset
  844. if li.RowOffset+1 >= li.Height && m.row < len(m.value)-1 {
  845. // Move to the next model line
  846. m.row++
  847. // We want to land on the first wrapped line of the new model line.
  848. grid := m.memoizedWrap(m.value[m.row], m.width)
  849. targetLineContent := grid[0]
  850. // Find position within the first wrapped line.
  851. offset := 0
  852. colInLine := 0
  853. for i, item := range targetLineContent {
  854. var itemWidth int
  855. switch v := item.(type) {
  856. case rune:
  857. itemWidth = rw.RuneWidth(v)
  858. case *attachment.Attachment:
  859. itemWidth = uniseg.StringWidth(v.Display)
  860. }
  861. if offset+itemWidth > charOffset {
  862. // Decide whether to stick with the previous index or move to the current
  863. // one based on which is closer to the target offset.
  864. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  865. colInLine = i + 1
  866. } else {
  867. colInLine = i
  868. }
  869. goto foundNextLine
  870. }
  871. offset += itemWidth
  872. }
  873. colInLine = len(targetLineContent)
  874. foundNextLine:
  875. m.col = colInLine // startCol is 0 for the first wrapped line
  876. } else if li.RowOffset+1 < li.Height {
  877. // Move to the next wrapped line within the same model line
  878. grid := m.memoizedWrap(m.value[m.row], m.width)
  879. targetLineContent := grid[li.RowOffset+1]
  880. startCol := 0
  881. for i := 0; i < li.RowOffset+1; i++ {
  882. startCol += len(grid[i])
  883. }
  884. // Find position within the target wrapped line.
  885. offset := 0
  886. colInLine := 0
  887. for i, item := range targetLineContent {
  888. var itemWidth int
  889. switch v := item.(type) {
  890. case rune:
  891. itemWidth = rw.RuneWidth(v)
  892. case *attachment.Attachment:
  893. itemWidth = uniseg.StringWidth(v.Display)
  894. }
  895. if offset+itemWidth > charOffset {
  896. // Decide whether to stick with the previous index or move to the current
  897. // one based on which is closer to the target offset.
  898. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  899. colInLine = i + 1
  900. } else {
  901. colInLine = i
  902. }
  903. goto foundSameLine
  904. }
  905. offset += itemWidth
  906. }
  907. colInLine = len(targetLineContent)
  908. foundSameLine:
  909. m.col = startCol + colInLine
  910. }
  911. m.SetCursorColumn(m.col)
  912. }
  913. // CursorUp moves the cursor up by one line.
  914. func (m *Model) CursorUp() {
  915. li := m.LineInfo()
  916. charOffset := max(m.lastCharOffset, li.CharOffset)
  917. m.lastCharOffset = charOffset
  918. if li.RowOffset <= 0 && m.row > 0 {
  919. // Move to the previous model line. We want to land on the last wrapped
  920. // line of the previous model line.
  921. m.row--
  922. grid := m.memoizedWrap(m.value[m.row], m.width)
  923. targetLineContent := grid[len(grid)-1]
  924. // Find start of last wrapped line.
  925. startCol := len(m.value[m.row]) - len(targetLineContent)
  926. // Find position within the last wrapped line.
  927. offset := 0
  928. colInLine := 0
  929. for i, item := range targetLineContent {
  930. var itemWidth int
  931. switch v := item.(type) {
  932. case rune:
  933. itemWidth = rw.RuneWidth(v)
  934. case *attachment.Attachment:
  935. itemWidth = uniseg.StringWidth(v.Display)
  936. }
  937. if offset+itemWidth > charOffset {
  938. // Decide whether to stick with the previous index or move to the current
  939. // one based on which is closer to the target offset.
  940. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  941. colInLine = i + 1
  942. } else {
  943. colInLine = i
  944. }
  945. goto foundPrevLine
  946. }
  947. offset += itemWidth
  948. }
  949. colInLine = len(targetLineContent)
  950. foundPrevLine:
  951. m.col = startCol + colInLine
  952. } else if li.RowOffset > 0 {
  953. // Move to the previous wrapped line within the same model line.
  954. grid := m.memoizedWrap(m.value[m.row], m.width)
  955. targetLineContent := grid[li.RowOffset-1]
  956. startCol := 0
  957. for i := 0; i < li.RowOffset-1; i++ {
  958. startCol += len(grid[i])
  959. }
  960. // Find position within the target wrapped line.
  961. offset := 0
  962. colInLine := 0
  963. for i, item := range targetLineContent {
  964. var itemWidth int
  965. switch v := item.(type) {
  966. case rune:
  967. itemWidth = rw.RuneWidth(v)
  968. case *attachment.Attachment:
  969. itemWidth = uniseg.StringWidth(v.Display)
  970. }
  971. if offset+itemWidth > charOffset {
  972. // Decide whether to stick with the previous index or move to the current
  973. // one based on which is closer to the target offset.
  974. if (charOffset - offset) > ((offset + itemWidth) - charOffset) {
  975. colInLine = i + 1
  976. } else {
  977. colInLine = i
  978. }
  979. goto foundSameLine
  980. }
  981. offset += itemWidth
  982. }
  983. colInLine = len(targetLineContent)
  984. foundSameLine:
  985. m.col = startCol + colInLine
  986. }
  987. m.SetCursorColumn(m.col)
  988. }
  989. // SetCursorColumn moves the cursor to the given position. If the position is
  990. // out of bounds the cursor will be moved to the start or end accordingly.
  991. func (m *Model) SetCursorColumn(col int) {
  992. m.col = clamp(col, 0, len(m.value[m.row]))
  993. // Any time that we move the cursor horizontally we need to reset the last
  994. // offset so that the horizontal position when navigating is adjusted.
  995. m.lastCharOffset = 0
  996. }
  997. // CursorStart moves the cursor to the start of the input field.
  998. func (m *Model) CursorStart() {
  999. m.SetCursorColumn(0)
  1000. }
  1001. // CursorEnd moves the cursor to the end of the input field.
  1002. func (m *Model) CursorEnd() {
  1003. m.SetCursorColumn(len(m.value[m.row]))
  1004. }
  1005. func (m *Model) IsCursorAtEnd() bool {
  1006. return m.CursorColumn() == len(m.value[m.row])
  1007. }
  1008. // Focused returns the focus state on the model.
  1009. func (m Model) Focused() bool {
  1010. return m.focus
  1011. }
  1012. // activeStyle returns the appropriate set of styles to use depending on
  1013. // whether the textarea is focused or blurred.
  1014. func (m Model) activeStyle() *StyleState {
  1015. if m.focus {
  1016. return &m.Styles.Focused
  1017. }
  1018. return &m.Styles.Blurred
  1019. }
  1020. // Focus sets the focus state on the model. When the model is in focus it can
  1021. // receive keyboard input and the cursor will be hidden.
  1022. func (m *Model) Focus() tea.Cmd {
  1023. m.focus = true
  1024. return m.virtualCursor.Focus()
  1025. }
  1026. // Blur removes the focus state on the model. When the model is blurred it can
  1027. // not receive keyboard input and the cursor will be hidden.
  1028. func (m *Model) Blur() {
  1029. m.focus = false
  1030. m.virtualCursor.Blur()
  1031. }
  1032. // Reset sets the input to its default state with no input.
  1033. func (m *Model) Reset() {
  1034. m.value = make([][]any, minHeight, maxLines)
  1035. m.col = 0
  1036. m.row = 0
  1037. m.SetCursorColumn(0)
  1038. }
  1039. // san initializes or retrieves the rune sanitizer.
  1040. func (m *Model) san() Sanitizer {
  1041. if m.rsan == nil {
  1042. // Textinput has all its input on a single line so collapse
  1043. // newlines/tabs to single spaces.
  1044. m.rsan = NewSanitizer()
  1045. }
  1046. return m.rsan
  1047. }
  1048. // deleteBeforeCursor deletes all text before the cursor. Returns whether or
  1049. // not the cursor blink should be reset.
  1050. func (m *Model) deleteBeforeCursor() {
  1051. m.value[m.row] = m.value[m.row][m.col:]
  1052. m.SetCursorColumn(0)
  1053. }
  1054. // deleteAfterCursor deletes all text after the cursor. Returns whether or not
  1055. // the cursor blink should be reset. If input is masked delete everything after
  1056. // the cursor so as not to reveal word breaks in the masked input.
  1057. func (m *Model) deleteAfterCursor() {
  1058. m.value[m.row] = m.value[m.row][:m.col]
  1059. m.SetCursorColumn(len(m.value[m.row]))
  1060. }
  1061. // transposeLeft exchanges the runes at the cursor and immediately
  1062. // before. No-op if the cursor is at the beginning of the line. If
  1063. // the cursor is not at the end of the line yet, moves the cursor to
  1064. // the right.
  1065. func (m *Model) transposeLeft() {
  1066. if m.col == 0 || len(m.value[m.row]) < 2 {
  1067. return
  1068. }
  1069. if m.col >= len(m.value[m.row]) {
  1070. m.SetCursorColumn(m.col - 1)
  1071. }
  1072. 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]
  1073. if m.col < len(m.value[m.row]) {
  1074. m.SetCursorColumn(m.col + 1)
  1075. }
  1076. }
  1077. // deleteWordLeft deletes the word left to the cursor. Returns whether or not
  1078. // the cursor blink should be reset.
  1079. func (m *Model) deleteWordLeft() {
  1080. if m.col == 0 || len(m.value[m.row]) == 0 {
  1081. return
  1082. }
  1083. // Linter note: it's critical that we acquire the initial cursor position
  1084. // here prior to altering it via SetCursor() below. As such, moving this
  1085. // call into the corresponding if clause does not apply here.
  1086. oldCol := m.col //nolint:ifshort
  1087. m.SetCursorColumn(m.col - 1)
  1088. for isSpaceAt(m.value[m.row], m.col) {
  1089. if m.col <= 0 {
  1090. break
  1091. }
  1092. // ignore series of whitespace before cursor
  1093. m.SetCursorColumn(m.col - 1)
  1094. }
  1095. for m.col > 0 {
  1096. if !isSpaceAt(m.value[m.row], m.col) {
  1097. m.SetCursorColumn(m.col - 1)
  1098. } else {
  1099. if m.col > 0 {
  1100. // keep the previous space
  1101. m.SetCursorColumn(m.col + 1)
  1102. }
  1103. break
  1104. }
  1105. }
  1106. if oldCol > len(m.value[m.row]) {
  1107. m.value[m.row] = m.value[m.row][:m.col]
  1108. } else {
  1109. m.value[m.row] = append(m.value[m.row][:m.col], m.value[m.row][oldCol:]...)
  1110. }
  1111. }
  1112. // deleteWordRight deletes the word right to the cursor.
  1113. func (m *Model) deleteWordRight() {
  1114. if m.col >= len(m.value[m.row]) || len(m.value[m.row]) == 0 {
  1115. return
  1116. }
  1117. oldCol := m.col
  1118. for m.col < len(m.value[m.row]) && isSpaceAt(m.value[m.row], m.col) {
  1119. // ignore series of whitespace after cursor
  1120. m.SetCursorColumn(m.col + 1)
  1121. }
  1122. for m.col < len(m.value[m.row]) {
  1123. if !isSpaceAt(m.value[m.row], m.col) {
  1124. m.SetCursorColumn(m.col + 1)
  1125. } else {
  1126. break
  1127. }
  1128. }
  1129. if m.col > len(m.value[m.row]) {
  1130. m.value[m.row] = m.value[m.row][:oldCol]
  1131. } else {
  1132. m.value[m.row] = append(m.value[m.row][:oldCol], m.value[m.row][m.col:]...)
  1133. }
  1134. m.SetCursorColumn(oldCol)
  1135. }
  1136. // characterRight moves the cursor one character to the right.
  1137. func (m *Model) characterRight() {
  1138. if m.col < len(m.value[m.row]) {
  1139. m.SetCursorColumn(m.col + 1)
  1140. } else {
  1141. if m.row < len(m.value)-1 {
  1142. m.row++
  1143. m.CursorStart()
  1144. }
  1145. }
  1146. }
  1147. // characterLeft moves the cursor one character to the left.
  1148. // If insideLine is set, the cursor is moved to the last
  1149. // character in the previous line, instead of one past that.
  1150. func (m *Model) characterLeft(insideLine bool) {
  1151. if m.col == 0 && m.row != 0 {
  1152. m.row--
  1153. m.CursorEnd()
  1154. if !insideLine {
  1155. return
  1156. }
  1157. }
  1158. if m.col > 0 {
  1159. m.SetCursorColumn(m.col - 1)
  1160. }
  1161. }
  1162. // wordLeft moves the cursor one word to the left. Returns whether or not the
  1163. // cursor blink should be reset. If input is masked, move input to the start
  1164. // so as not to reveal word breaks in the masked input.
  1165. func (m *Model) wordLeft() {
  1166. for {
  1167. m.characterLeft(true /* insideLine */)
  1168. if m.col < len(m.value[m.row]) && !isSpaceAt(m.value[m.row], m.col) {
  1169. break
  1170. }
  1171. }
  1172. for m.col > 0 {
  1173. if isSpaceAt(m.value[m.row], m.col-1) {
  1174. break
  1175. }
  1176. m.SetCursorColumn(m.col - 1)
  1177. }
  1178. }
  1179. // wordRight moves the cursor one word to the right. Returns whether or not the
  1180. // cursor blink should be reset. If the input is masked, move input to the end
  1181. // so as not to reveal word breaks in the masked input.
  1182. func (m *Model) wordRight() {
  1183. m.doWordRight(func(int, int) { /* nothing */ })
  1184. }
  1185. func (m *Model) doWordRight(fn func(charIdx int, pos int)) {
  1186. // Skip spaces forward.
  1187. for m.col >= len(m.value[m.row]) || isSpaceAt(m.value[m.row], m.col) {
  1188. if m.row == len(m.value)-1 && m.col == len(m.value[m.row]) {
  1189. // End of text.
  1190. break
  1191. }
  1192. m.characterRight()
  1193. }
  1194. charIdx := 0
  1195. for m.col < len(m.value[m.row]) {
  1196. if isSpaceAt(m.value[m.row], m.col) {
  1197. break
  1198. }
  1199. fn(charIdx, m.col)
  1200. m.SetCursorColumn(m.col + 1)
  1201. charIdx++
  1202. }
  1203. }
  1204. // uppercaseRight changes the word to the right to uppercase.
  1205. func (m *Model) uppercaseRight() {
  1206. m.doWordRight(func(_ int, i int) {
  1207. if r, ok := m.value[m.row][i].(rune); ok {
  1208. m.value[m.row][i] = unicode.ToUpper(r)
  1209. }
  1210. })
  1211. }
  1212. // lowercaseRight changes the word to the right to lowercase.
  1213. func (m *Model) lowercaseRight() {
  1214. m.doWordRight(func(_ int, i int) {
  1215. if r, ok := m.value[m.row][i].(rune); ok {
  1216. m.value[m.row][i] = unicode.ToLower(r)
  1217. }
  1218. })
  1219. }
  1220. // capitalizeRight changes the word to the right to title case.
  1221. func (m *Model) capitalizeRight() {
  1222. m.doWordRight(func(charIdx int, i int) {
  1223. if charIdx == 0 {
  1224. if r, ok := m.value[m.row][i].(rune); ok {
  1225. m.value[m.row][i] = unicode.ToTitle(r)
  1226. }
  1227. }
  1228. })
  1229. }
  1230. // LineInfo returns the number of characters from the start of the
  1231. // (soft-wrapped) line and the (soft-wrapped) line width.
  1232. func (m Model) LineInfo() LineInfo {
  1233. grid := m.memoizedWrap(m.value[m.row], m.width)
  1234. // Find out which line we are currently on. This can be determined by the
  1235. // m.col and counting the number of runes that we need to skip.
  1236. var counter int
  1237. for i, line := range grid {
  1238. start := counter
  1239. end := counter + len(line)
  1240. if m.col >= start && m.col <= end {
  1241. // This is the wrapped line the cursor is on.
  1242. // Special case: if the cursor is at the end of a wrapped line,
  1243. // and there's another wrapped line after it, the cursor should
  1244. // be considered at the beginning of the next line.
  1245. if m.col == end && i < len(grid)-1 {
  1246. nextLine := grid[i+1]
  1247. return LineInfo{
  1248. CharOffset: 0,
  1249. ColumnOffset: 0,
  1250. Height: len(grid),
  1251. RowOffset: i + 1,
  1252. StartColumn: end,
  1253. Width: len(nextLine),
  1254. CharWidth: uniseg.StringWidth(interfacesToString(nextLine)),
  1255. }
  1256. }
  1257. return LineInfo{
  1258. CharOffset: uniseg.StringWidth(interfacesToString(line[:max(0, m.col-start)])),
  1259. ColumnOffset: m.col - start,
  1260. Height: len(grid),
  1261. RowOffset: i,
  1262. StartColumn: start,
  1263. Width: len(line),
  1264. CharWidth: uniseg.StringWidth(interfacesToString(line)),
  1265. }
  1266. }
  1267. counter = end
  1268. }
  1269. return LineInfo{}
  1270. }
  1271. // Width returns the width of the textarea.
  1272. func (m Model) Width() int {
  1273. return m.width
  1274. }
  1275. // MoveToBegin moves the cursor to the beginning of the input.
  1276. func (m *Model) MoveToBegin() {
  1277. m.row = 0
  1278. m.SetCursorColumn(0)
  1279. }
  1280. // MoveToEnd moves the cursor to the end of the input.
  1281. func (m *Model) MoveToEnd() {
  1282. m.row = len(m.value) - 1
  1283. m.SetCursorColumn(len(m.value[m.row]))
  1284. }
  1285. // SetWidth sets the width of the textarea to fit exactly within the given width.
  1286. // This means that the textarea will account for the width of the prompt and
  1287. // whether or not line numbers are being shown.
  1288. //
  1289. // Ensure that SetWidth is called after setting the Prompt and ShowLineNumbers,
  1290. // It is important that the width of the textarea be exactly the given width
  1291. // and no more.
  1292. func (m *Model) SetWidth(w int) {
  1293. // Update prompt width only if there is no prompt function as
  1294. // [SetPromptFunc] updates the prompt width when it is called.
  1295. if m.promptFunc == nil {
  1296. // XXX: Do we even need this or can we calculate the prompt width
  1297. // at render time?
  1298. m.promptWidth = uniseg.StringWidth(m.Prompt)
  1299. }
  1300. // Add base style borders and padding to reserved outer width.
  1301. reservedOuter := m.activeStyle().Base.GetHorizontalFrameSize()
  1302. // Add prompt width to reserved inner width.
  1303. reservedInner := m.promptWidth
  1304. // Add line number width to reserved inner width.
  1305. if m.ShowLineNumbers {
  1306. // XXX: this was originally documented as needing "1 cell" but was,
  1307. // in practice, effectively hardcoded to 2 cells. We can, and should,
  1308. // reduce this to one gap and update the tests accordingly.
  1309. const gap = 2
  1310. // Number of digits plus 1 cell for the margin.
  1311. reservedInner += numDigits(m.MaxHeight) + gap
  1312. }
  1313. // Input width must be at least one more than the reserved inner and outer
  1314. // width. This gives us a minimum input width of 1.
  1315. minWidth := reservedInner + reservedOuter + 1
  1316. inputWidth := max(w, minWidth)
  1317. // Input width must be no more than maximum width.
  1318. if m.MaxWidth > 0 {
  1319. inputWidth = min(inputWidth, m.MaxWidth)
  1320. }
  1321. // Since the width of the viewport and input area is dependent on the width of
  1322. // borders, prompt and line numbers, we need to calculate it by subtracting
  1323. // the reserved width from them.
  1324. m.width = inputWidth - reservedOuter - reservedInner
  1325. }
  1326. // SetPromptFunc supersedes the Prompt field and sets a dynamic prompt instead.
  1327. //
  1328. // If the function returns a prompt that is shorter than the specified
  1329. // promptWidth, it will be padded to the left. If it returns a prompt that is
  1330. // longer, display artifacts may occur; the caller is responsible for computing
  1331. // an adequate promptWidth.
  1332. func (m *Model) SetPromptFunc(promptWidth int, fn func(lineIndex int) string) {
  1333. m.promptFunc = fn
  1334. m.promptWidth = promptWidth
  1335. }
  1336. // Height returns the current height of the textarea.
  1337. func (m Model) Height() int {
  1338. return m.height
  1339. }
  1340. // ContentHeight returns the actual height needed to display all content
  1341. // including wrapped lines.
  1342. func (m Model) ContentHeight() int {
  1343. totalLines := 0
  1344. for _, line := range m.value {
  1345. wrappedLines := m.memoizedWrap(line, m.width)
  1346. totalLines += len(wrappedLines)
  1347. }
  1348. // Ensure at least one line is shown
  1349. if totalLines == 0 {
  1350. totalLines = 1
  1351. }
  1352. return totalLines
  1353. }
  1354. // SetHeight sets the height of the textarea.
  1355. func (m *Model) SetHeight(h int) {
  1356. // Calculate the actual content height
  1357. contentHeight := m.ContentHeight()
  1358. // Use the content height as the actual height
  1359. if m.MaxHeight > 0 {
  1360. m.height = clamp(contentHeight, minHeight, m.MaxHeight)
  1361. } else {
  1362. m.height = max(contentHeight, minHeight)
  1363. }
  1364. }
  1365. // Update is the Bubble Tea update loop.
  1366. func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
  1367. if !m.focus {
  1368. m.virtualCursor.Blur()
  1369. return m, nil
  1370. }
  1371. // Used to determine if the cursor should blink.
  1372. oldRow, oldCol := m.cursorLineNumber(), m.col
  1373. var cmds []tea.Cmd
  1374. if m.row >= len(m.value) {
  1375. m.value = append(m.value, make([]any, 0))
  1376. }
  1377. if m.value[m.row] == nil {
  1378. m.value[m.row] = make([]any, 0)
  1379. }
  1380. if m.MaxHeight > 0 && m.MaxHeight != m.cache.Capacity() {
  1381. m.cache = NewMemoCache[line, [][]any](m.MaxHeight)
  1382. }
  1383. switch msg := msg.(type) {
  1384. case tea.KeyPressMsg:
  1385. switch {
  1386. case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
  1387. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1388. if m.col >= len(m.value[m.row]) {
  1389. m.mergeLineBelow(m.row)
  1390. break
  1391. }
  1392. m.deleteAfterCursor()
  1393. case key.Matches(msg, m.KeyMap.DeleteBeforeCursor):
  1394. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1395. if m.col <= 0 {
  1396. m.mergeLineAbove(m.row)
  1397. break
  1398. }
  1399. m.deleteBeforeCursor()
  1400. case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
  1401. // If the cursor is at or just after an attachment, convert it to text instead of deleting
  1402. if att, _, _ := m.isAttachmentAtCursor(); att != nil {
  1403. if m.removeAttachmentAtCursor() {
  1404. break
  1405. }
  1406. }
  1407. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1408. if m.col <= 0 {
  1409. m.mergeLineAbove(m.row)
  1410. break
  1411. }
  1412. if len(m.value[m.row]) > 0 && m.col > 0 {
  1413. m.value[m.row] = slices.Delete(m.value[m.row], m.col-1, m.col)
  1414. m.SetCursorColumn(m.col - 1)
  1415. }
  1416. case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
  1417. // If the cursor is on an attachment, convert it to text instead of deleting
  1418. if att, _, _ := m.isAttachmentAtCursor(); att != nil {
  1419. if m.removeAttachmentAtCursor() {
  1420. break
  1421. }
  1422. }
  1423. if len(m.value[m.row]) > 0 && m.col < len(m.value[m.row]) {
  1424. m.value[m.row] = slices.Delete(m.value[m.row], m.col, m.col+1)
  1425. }
  1426. if m.col >= len(m.value[m.row]) {
  1427. m.mergeLineBelow(m.row)
  1428. break
  1429. }
  1430. case key.Matches(msg, m.KeyMap.DeleteWordBackward):
  1431. if m.col <= 0 {
  1432. m.mergeLineAbove(m.row)
  1433. break
  1434. }
  1435. m.deleteWordLeft()
  1436. case key.Matches(msg, m.KeyMap.DeleteWordForward):
  1437. m.col = clamp(m.col, 0, len(m.value[m.row]))
  1438. if m.col >= len(m.value[m.row]) {
  1439. m.mergeLineBelow(m.row)
  1440. break
  1441. }
  1442. m.deleteWordRight()
  1443. case key.Matches(msg, m.KeyMap.InsertNewline):
  1444. m.Newline()
  1445. case key.Matches(msg, m.KeyMap.LineEnd):
  1446. m.CursorEnd()
  1447. case key.Matches(msg, m.KeyMap.LineStart):
  1448. m.CursorStart()
  1449. case key.Matches(msg, m.KeyMap.CharacterForward):
  1450. m.characterRight()
  1451. case key.Matches(msg, m.KeyMap.LineNext):
  1452. m.CursorDown()
  1453. case key.Matches(msg, m.KeyMap.WordForward):
  1454. m.wordRight()
  1455. case key.Matches(msg, m.KeyMap.CharacterBackward):
  1456. m.characterLeft(false /* insideLine */)
  1457. case key.Matches(msg, m.KeyMap.LinePrevious):
  1458. m.CursorUp()
  1459. case key.Matches(msg, m.KeyMap.WordBackward):
  1460. m.wordLeft()
  1461. case key.Matches(msg, m.KeyMap.InputBegin):
  1462. m.MoveToBegin()
  1463. case key.Matches(msg, m.KeyMap.InputEnd):
  1464. m.MoveToEnd()
  1465. case key.Matches(msg, m.KeyMap.LowercaseWordForward):
  1466. m.lowercaseRight()
  1467. case key.Matches(msg, m.KeyMap.UppercaseWordForward):
  1468. m.uppercaseRight()
  1469. case key.Matches(msg, m.KeyMap.CapitalizeWordForward):
  1470. m.capitalizeRight()
  1471. case key.Matches(msg, m.KeyMap.TransposeCharacterBackward):
  1472. m.transposeLeft()
  1473. default:
  1474. m.InsertRunesFromUserInput([]rune(msg.Text))
  1475. }
  1476. case pasteMsg:
  1477. m.InsertRunesFromUserInput([]rune(msg))
  1478. case pasteErrMsg:
  1479. m.Err = msg
  1480. }
  1481. var cmd tea.Cmd
  1482. newRow, newCol := m.cursorLineNumber(), m.col
  1483. m.virtualCursor, cmd = m.virtualCursor.Update(msg)
  1484. if (newRow != oldRow || newCol != oldCol) && m.virtualCursor.Mode() == cursor.CursorBlink {
  1485. m.virtualCursor.Blink = false
  1486. cmd = m.virtualCursor.BlinkCmd()
  1487. }
  1488. cmds = append(cmds, cmd)
  1489. return m, tea.Batch(cmds...)
  1490. }
  1491. // View renders the text area in its current state.
  1492. func (m Model) View() string {
  1493. m.updateVirtualCursorStyle()
  1494. if m.Value() == "" && m.row == 0 && m.col == 0 && m.Placeholder != "" {
  1495. return m.placeholderView()
  1496. }
  1497. m.virtualCursor.TextStyle = m.activeStyle().computedCursorLine()
  1498. var (
  1499. s strings.Builder
  1500. style lipgloss.Style
  1501. newLines int
  1502. widestLineNumber int
  1503. lineInfo = m.LineInfo()
  1504. styles = m.activeStyle()
  1505. )
  1506. displayLine := 0
  1507. for l, line := range m.value {
  1508. wrappedLines := m.memoizedWrap(line, m.width)
  1509. if m.row == l {
  1510. style = styles.computedCursorLine()
  1511. } else {
  1512. style = styles.computedText()
  1513. }
  1514. for wl, wrappedLine := range wrappedLines {
  1515. prompt := m.promptView(displayLine)
  1516. prompt = styles.computedPrompt().Render(prompt)
  1517. s.WriteString(style.Render(prompt))
  1518. displayLine++
  1519. var ln string
  1520. if m.ShowLineNumbers {
  1521. if wl == 0 { // normal line
  1522. isCursorLine := m.row == l
  1523. s.WriteString(m.lineNumberView(l+1, isCursorLine))
  1524. } else { // soft wrapped line
  1525. isCursorLine := m.row == l
  1526. s.WriteString(m.lineNumberView(-1, isCursorLine))
  1527. }
  1528. }
  1529. // Note the widest line number for padding purposes later.
  1530. lnw := uniseg.StringWidth(ln)
  1531. if lnw > widestLineNumber {
  1532. widestLineNumber = lnw
  1533. }
  1534. wrappedLineStr := interfacesToString(wrappedLine)
  1535. strwidth := uniseg.StringWidth(wrappedLineStr)
  1536. padding := m.width - strwidth
  1537. // If the trailing space causes the line to be wider than the
  1538. // width, we should not draw it to the screen since it will result
  1539. // in an extra space at the end of the line which can look off when
  1540. // the cursor line is showing.
  1541. if strwidth > m.width {
  1542. // The character causing the line to be wider than the width is
  1543. // guaranteed to be a space since any other character would
  1544. // have been wrapped.
  1545. wrappedLineStr = strings.TrimSuffix(wrappedLineStr, " ")
  1546. padding = m.width - uniseg.StringWidth(wrappedLineStr)
  1547. }
  1548. if m.row == l && lineInfo.RowOffset == wl {
  1549. // Render the part of the line before the cursor
  1550. s.WriteString(
  1551. m.renderLineWithAttachments(
  1552. wrappedLine[:lineInfo.ColumnOffset],
  1553. style,
  1554. ),
  1555. )
  1556. if m.col >= len(line) && lineInfo.CharOffset >= m.width {
  1557. m.virtualCursor.SetChar(" ")
  1558. s.WriteString(m.virtualCursor.View())
  1559. } else if lineInfo.ColumnOffset < len(wrappedLine) {
  1560. // Render the item under the cursor
  1561. item := wrappedLine[lineInfo.ColumnOffset]
  1562. if att, ok := item.(*attachment.Attachment); ok {
  1563. // Item at cursor is an attachment. Render it with the selection style.
  1564. // This becomes the "cursor" visually.
  1565. s.WriteString(m.Styles.SelectedAttachment.Render(att.Display))
  1566. } else {
  1567. // Item at cursor is a rune. Render it with the virtual cursor.
  1568. m.virtualCursor.SetChar(string(item.(rune)))
  1569. s.WriteString(style.Render(m.virtualCursor.View()))
  1570. }
  1571. // Render the part of the line after the cursor
  1572. s.WriteString(m.renderLineWithAttachments(wrappedLine[lineInfo.ColumnOffset+1:], style))
  1573. } else {
  1574. // Cursor is at the end of the line
  1575. m.virtualCursor.SetChar(" ")
  1576. s.WriteString(style.Render(m.virtualCursor.View()))
  1577. }
  1578. } else {
  1579. s.WriteString(m.renderLineWithAttachments(wrappedLine, style))
  1580. }
  1581. s.WriteString(style.Render(strings.Repeat(" ", max(0, padding))))
  1582. s.WriteRune('\n')
  1583. newLines++
  1584. }
  1585. }
  1586. // Remove the trailing newline from the last line
  1587. result := s.String()
  1588. if len(result) > 0 && result[len(result)-1] == '\n' {
  1589. result = result[:len(result)-1]
  1590. }
  1591. return styles.Base.Render(result)
  1592. }
  1593. // promptView renders a single line of the prompt.
  1594. func (m Model) promptView(displayLine int) (prompt string) {
  1595. prompt = m.Prompt
  1596. if m.promptFunc == nil {
  1597. return prompt
  1598. }
  1599. prompt = m.promptFunc(displayLine)
  1600. width := lipgloss.Width(prompt)
  1601. if width < m.promptWidth {
  1602. prompt = fmt.Sprintf("%*s%s", m.promptWidth-width, "", prompt)
  1603. }
  1604. return m.activeStyle().computedPrompt().Render(prompt)
  1605. }
  1606. // lineNumberView renders the line number.
  1607. //
  1608. // If the argument is less than 0, a space styled as a line number is returned
  1609. // instead. Such cases are used for soft-wrapped lines.
  1610. //
  1611. // The second argument indicates whether this line number is for a 'cursorline'
  1612. // line number.
  1613. func (m Model) lineNumberView(n int, isCursorLine bool) (str string) {
  1614. if !m.ShowLineNumbers {
  1615. return ""
  1616. }
  1617. if n <= 0 {
  1618. str = " "
  1619. } else {
  1620. str = strconv.Itoa(n)
  1621. }
  1622. // XXX: is textStyle really necessary here?
  1623. textStyle := m.activeStyle().computedText()
  1624. lineNumberStyle := m.activeStyle().computedLineNumber()
  1625. if isCursorLine {
  1626. textStyle = m.activeStyle().computedCursorLine()
  1627. lineNumberStyle = m.activeStyle().computedCursorLineNumber()
  1628. }
  1629. // Format line number dynamically based on the maximum number of lines.
  1630. digits := len(strconv.Itoa(m.MaxHeight))
  1631. str = fmt.Sprintf(" %*v ", digits, str)
  1632. return textStyle.Render(lineNumberStyle.Render(str))
  1633. }
  1634. // placeholderView returns the prompt and placeholder, if any.
  1635. func (m Model) placeholderView() string {
  1636. var (
  1637. s strings.Builder
  1638. p = m.Placeholder
  1639. styles = m.activeStyle()
  1640. )
  1641. // word wrap lines
  1642. pwordwrap := ansi.Wordwrap(p, m.width, "")
  1643. // hard wrap lines (handles lines that could not be word wrapped)
  1644. pwrap := ansi.Hardwrap(pwordwrap, m.width, true)
  1645. // split string by new lines
  1646. plines := strings.Split(strings.TrimSpace(pwrap), "\n")
  1647. // Only render the actual placeholder lines, not padded to m.height
  1648. maxLines := max(len(plines), 1) // At least show one line for cursor
  1649. for i := range maxLines {
  1650. isLineNumber := len(plines) > i
  1651. lineStyle := styles.computedPlaceholder()
  1652. if len(plines) > i {
  1653. lineStyle = styles.computedCursorLine()
  1654. }
  1655. // render prompt
  1656. prompt := m.promptView(i)
  1657. prompt = styles.computedPrompt().Render(prompt)
  1658. s.WriteString(lineStyle.Render(prompt))
  1659. // when show line numbers enabled:
  1660. // - render line number for only the cursor line
  1661. // - indent other placeholder lines
  1662. // this is consistent with vim with line numbers enabled
  1663. if m.ShowLineNumbers {
  1664. var ln int
  1665. switch {
  1666. case i == 0:
  1667. ln = i + 1
  1668. fallthrough
  1669. case len(plines) > i:
  1670. s.WriteString(m.lineNumberView(ln, isLineNumber))
  1671. default:
  1672. }
  1673. }
  1674. switch {
  1675. // first line
  1676. case i == 0:
  1677. // first character of first line as cursor with character
  1678. m.virtualCursor.TextStyle = styles.computedPlaceholder()
  1679. m.virtualCursor.SetChar(string(plines[0][0]))
  1680. s.WriteString(lineStyle.Render(m.virtualCursor.View()))
  1681. // the rest of the first line
  1682. placeholderTail := plines[0][1:]
  1683. gap := strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[0])))
  1684. renderedPlaceholder := styles.computedPlaceholder().Render(placeholderTail + gap)
  1685. s.WriteString(lineStyle.Render(renderedPlaceholder))
  1686. // remaining lines
  1687. case len(plines) > i:
  1688. // current line placeholder text
  1689. if len(plines) > i {
  1690. placeholderLine := plines[i]
  1691. gap := strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[i])))
  1692. s.WriteString(lineStyle.Render(placeholderLine + gap))
  1693. }
  1694. default:
  1695. // end of line buffer character
  1696. eob := styles.computedEndOfBuffer().Render(string(m.EndOfBufferCharacter))
  1697. s.WriteString(eob)
  1698. }
  1699. // terminate with new line (except for last line)
  1700. if i < maxLines-1 {
  1701. s.WriteRune('\n')
  1702. }
  1703. }
  1704. return styles.Base.Render(s.String())
  1705. }
  1706. // Blink returns the blink command for the virtual cursor.
  1707. func Blink() tea.Msg {
  1708. return cursor.Blink()
  1709. }
  1710. // Cursor returns a [tea.Cursor] for rendering a real cursor in a Bubble Tea
  1711. // program. This requires that [Model.VirtualCursor] is set to false.
  1712. //
  1713. // Note that you will almost certainly also need to adjust the offset cursor
  1714. // position per the textarea's per the textarea's position in the terminal.
  1715. //
  1716. // Example:
  1717. //
  1718. // // In your top-level View function:
  1719. // f := tea.NewFrame(m.textarea.View())
  1720. // f.Cursor = m.textarea.Cursor()
  1721. // f.Cursor.Position.X += offsetX
  1722. // f.Cursor.Position.Y += offsetY
  1723. func (m Model) Cursor() *tea.Cursor {
  1724. if m.VirtualCursor {
  1725. return nil
  1726. }
  1727. lineInfo := m.LineInfo()
  1728. w := lipgloss.Width
  1729. baseStyle := m.activeStyle().Base
  1730. xOffset := lineInfo.CharOffset +
  1731. w(m.promptView(0)) +
  1732. w(m.lineNumberView(0, false)) +
  1733. baseStyle.GetMarginLeft() +
  1734. baseStyle.GetPaddingLeft() +
  1735. baseStyle.GetBorderLeftSize()
  1736. yOffset := m.cursorLineNumber() -
  1737. baseStyle.GetMarginTop() +
  1738. baseStyle.GetPaddingTop() +
  1739. baseStyle.GetBorderTopSize()
  1740. c := tea.NewCursor(xOffset, yOffset)
  1741. c.Blink = m.Styles.Cursor.Blink
  1742. c.Color = m.Styles.Cursor.Color
  1743. c.Shape = m.Styles.Cursor.Shape
  1744. return c
  1745. }
  1746. func (m Model) memoizedWrap(content []any, width int) [][]any {
  1747. input := line{content: content, width: width}
  1748. if v, ok := m.cache.Get(input); ok {
  1749. return v
  1750. }
  1751. v := wrapInterfaces(content, width)
  1752. m.cache.Set(input, v)
  1753. return v
  1754. }
  1755. // cursorLineNumber returns the line number that the cursor is on.
  1756. // This accounts for soft wrapped lines.
  1757. func (m Model) cursorLineNumber() int {
  1758. line := 0
  1759. for i := range m.row {
  1760. // Calculate the number of lines that the current line will be split
  1761. // into.
  1762. line += len(m.memoizedWrap(m.value[i], m.width))
  1763. }
  1764. line += m.LineInfo().RowOffset
  1765. return line
  1766. }
  1767. // mergeLineBelow merges the current line the cursor is on with the line below.
  1768. func (m *Model) mergeLineBelow(row int) {
  1769. if row >= len(m.value)-1 {
  1770. return
  1771. }
  1772. // To perform a merge, we will need to combine the two lines and then
  1773. m.value[row] = append(m.value[row], m.value[row+1]...)
  1774. // Shift all lines up by one
  1775. for i := row + 1; i < len(m.value)-1; i++ {
  1776. m.value[i] = m.value[i+1]
  1777. }
  1778. // And, remove the last line
  1779. if len(m.value) > 0 {
  1780. m.value = m.value[:len(m.value)-1]
  1781. }
  1782. }
  1783. // mergeLineAbove merges the current line the cursor is on with the line above.
  1784. func (m *Model) mergeLineAbove(row int) {
  1785. if row <= 0 {
  1786. return
  1787. }
  1788. m.col = len(m.value[row-1])
  1789. m.row = m.row - 1
  1790. // To perform a merge, we will need to combine the two lines and then
  1791. m.value[row-1] = append(m.value[row-1], m.value[row]...)
  1792. // Shift all lines up by one
  1793. for i := row; i < len(m.value)-1; i++ {
  1794. m.value[i] = m.value[i+1]
  1795. }
  1796. // And, remove the last line
  1797. if len(m.value) > 0 {
  1798. m.value = m.value[:len(m.value)-1]
  1799. }
  1800. }
  1801. func (m *Model) splitLine(row, col int) {
  1802. // To perform a split, take the current line and keep the content before
  1803. // the cursor, take the content after the cursor and make it the content of
  1804. // the line underneath, and shift the remaining lines down by one
  1805. head, tailSrc := m.value[row][:col], m.value[row][col:]
  1806. tail := copyInterfaceSlice(tailSrc)
  1807. m.value = append(m.value[:row+1], m.value[row:]...)
  1808. m.value[row] = head
  1809. m.value[row+1] = tail
  1810. m.col = 0
  1811. m.row++
  1812. }
  1813. func itemWidth(item any) int {
  1814. switch v := item.(type) {
  1815. case rune:
  1816. return rw.RuneWidth(v)
  1817. case *attachment.Attachment:
  1818. return uniseg.StringWidth(v.Display)
  1819. }
  1820. return 0
  1821. }
  1822. // forceWrapAttachment splits an attachment's display text across multiple lines
  1823. func forceWrapAttachment(att *attachment.Attachment, width int) [][]any {
  1824. if width <= 0 {
  1825. return [][]any{{att}}
  1826. }
  1827. display := att.Display
  1828. displayRunes := []rune(display)
  1829. if len(displayRunes) <= width {
  1830. return [][]any{{att}}
  1831. }
  1832. var lines [][]any
  1833. start := 0
  1834. for start < len(displayRunes) {
  1835. // Calculate how many runes fit in this line
  1836. end := start + width
  1837. if end > len(displayRunes) {
  1838. end = len(displayRunes)
  1839. }
  1840. // Create a wrapped attachment for this segment
  1841. wrappedAtt := &attachment.Attachment{
  1842. ID: att.ID,
  1843. Type: att.Type,
  1844. Display: string(displayRunes[start:end]),
  1845. URL: att.URL,
  1846. Filename: att.Filename,
  1847. MediaType: att.MediaType,
  1848. Source: att.Source,
  1849. }
  1850. lines = append(lines, []any{wrappedAtt})
  1851. start = end
  1852. }
  1853. return lines
  1854. }
  1855. // forceWrapWord splits a word that's too long to fit within the given width
  1856. func forceWrapWord(word []any, width int) [][]any {
  1857. if width <= 0 || len(word) == 0 {
  1858. return [][]any{word}
  1859. }
  1860. var lines [][]any
  1861. currentLine := []any{}
  1862. currentWidth := 0
  1863. for _, item := range word {
  1864. if att, ok := item.(*attachment.Attachment); ok {
  1865. // Handle attachment that might be too wide
  1866. attWidth := uniseg.StringWidth(att.Display)
  1867. // If the attachment display is too wide, split it
  1868. if attWidth > width {
  1869. // Finish current line if it has content
  1870. if len(currentLine) > 0 {
  1871. lines = append(lines, currentLine)
  1872. currentLine = []any{}
  1873. currentWidth = 0
  1874. }
  1875. // Split the attachment display across multiple lines
  1876. wrappedAttachment := forceWrapAttachment(att, width)
  1877. lines = append(lines, wrappedAttachment...)
  1878. continue
  1879. }
  1880. // If adding this attachment would exceed the width, start a new line
  1881. if currentWidth+attWidth > width && len(currentLine) > 0 {
  1882. lines = append(lines, currentLine)
  1883. currentLine = []any{}
  1884. currentWidth = 0
  1885. }
  1886. currentLine = append(currentLine, item)
  1887. currentWidth += attWidth
  1888. } else if r, ok := item.(rune); ok {
  1889. itemWidth := rw.RuneWidth(r)
  1890. // If adding this rune would exceed the width, start a new line
  1891. if currentWidth+itemWidth > width && len(currentLine) > 0 {
  1892. lines = append(lines, currentLine)
  1893. currentLine = []any{}
  1894. currentWidth = 0
  1895. }
  1896. currentLine = append(currentLine, item)
  1897. currentWidth += itemWidth
  1898. }
  1899. }
  1900. // Add the last line if it has content
  1901. if len(currentLine) > 0 {
  1902. lines = append(lines, currentLine)
  1903. }
  1904. return lines
  1905. }
  1906. func wrapInterfaces(content []any, width int) [][]any {
  1907. if width <= 0 {
  1908. return [][]any{content}
  1909. }
  1910. var (
  1911. lines = [][]any{{}}
  1912. word = []any{}
  1913. wordW int
  1914. lineW int
  1915. spaceW int
  1916. inSpaces bool
  1917. )
  1918. for _, item := range content {
  1919. itemW := 0
  1920. isSpace := false
  1921. if r, ok := item.(rune); ok {
  1922. if unicode.IsSpace(r) {
  1923. isSpace = true
  1924. }
  1925. itemW = rw.RuneWidth(r)
  1926. } else if att, ok := item.(*attachment.Attachment); ok {
  1927. itemW = uniseg.StringWidth(att.Display)
  1928. }
  1929. if isSpace {
  1930. if !inSpaces {
  1931. // End of a word
  1932. if lineW > 0 && lineW+wordW > width {
  1933. // If the word itself is too long to fit on a line, force-wrap it
  1934. if wordW > width {
  1935. wrappedLines := forceWrapWord(word, width)
  1936. lines = append(lines, wrappedLines...)
  1937. // Calculate width of the last wrapped line
  1938. lastLine := wrappedLines[len(wrappedLines)-1]
  1939. lineW = 0
  1940. for _, item := range lastLine {
  1941. if r, ok := item.(rune); ok {
  1942. lineW += rw.RuneWidth(r)
  1943. } else if att, ok := item.(*attachment.Attachment); ok {
  1944. lineW += uniseg.StringWidth(att.Display)
  1945. }
  1946. }
  1947. } else {
  1948. lines = append(lines, word)
  1949. lineW = wordW
  1950. }
  1951. } else {
  1952. // Check if the word needs to be force-wrapped even when it fits on the current line
  1953. if wordW > width {
  1954. currentLine := lines[len(lines)-1]
  1955. wrappedWord := forceWrapWord(word, width-lineW)
  1956. if len(wrappedWord) > 0 {
  1957. lines[len(lines)-1] = append(currentLine, wrappedWord[0]...)
  1958. for i := 1; i < len(wrappedWord); i++ {
  1959. lines = append(lines, wrappedWord[i])
  1960. }
  1961. // Calculate width of the last wrapped line
  1962. lastLine := wrappedWord[len(wrappedWord)-1]
  1963. lineW = 0
  1964. for _, item := range lastLine {
  1965. if r, ok := item.(rune); ok {
  1966. lineW += rw.RuneWidth(r)
  1967. } else if att, ok := item.(*attachment.Attachment); ok {
  1968. lineW += uniseg.StringWidth(att.Display)
  1969. }
  1970. }
  1971. }
  1972. } else {
  1973. lines[len(lines)-1] = append(lines[len(lines)-1], word...)
  1974. lineW += wordW
  1975. }
  1976. }
  1977. word = nil
  1978. wordW = 0
  1979. }
  1980. inSpaces = true
  1981. spaceW += itemW
  1982. } else { // It's not a space, it's a character for a word.
  1983. if inSpaces {
  1984. // We just finished a block of spaces. Handle them now.
  1985. lineW += spaceW
  1986. for i := 0; i < spaceW; i++ {
  1987. lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
  1988. }
  1989. if lineW > width {
  1990. // The spaces made the line overflow. Start a new line for the upcoming word.
  1991. lines = append(lines, []any{})
  1992. lineW = 0
  1993. }
  1994. spaceW = 0
  1995. }
  1996. inSpaces = false
  1997. word = append(word, item)
  1998. wordW += itemW
  1999. }
  2000. }
  2001. // Handle any remaining word/spaces at the end of the content.
  2002. if wordW > 0 {
  2003. if lineW > 0 && lineW+wordW > width {
  2004. // If the word itself is too long to fit on a line, force-wrap it
  2005. if wordW > width {
  2006. wrappedLines := forceWrapWord(word, width)
  2007. lines = append(lines, wrappedLines...)
  2008. // Calculate width of the last wrapped line
  2009. lastLine := wrappedLines[len(wrappedLines)-1]
  2010. lineW = 0
  2011. for _, item := range lastLine {
  2012. if r, ok := item.(rune); ok {
  2013. lineW += rw.RuneWidth(r)
  2014. } else if att, ok := item.(*attachment.Attachment); ok {
  2015. lineW += uniseg.StringWidth(att.Display)
  2016. }
  2017. }
  2018. } else {
  2019. lines = append(lines, word)
  2020. lineW = wordW
  2021. }
  2022. } else {
  2023. // Check if the word needs to be force-wrapped even when it fits on the current line
  2024. if wordW > width {
  2025. currentLine := lines[len(lines)-1]
  2026. wrappedWord := forceWrapWord(word, width-lineW)
  2027. if len(wrappedWord) > 0 {
  2028. lines[len(lines)-1] = append(currentLine, wrappedWord[0]...)
  2029. for i := 1; i < len(wrappedWord); i++ {
  2030. lines = append(lines, wrappedWord[i])
  2031. }
  2032. // Calculate width of the last wrapped line
  2033. lastLine := wrappedWord[len(wrappedWord)-1]
  2034. lineW = 0
  2035. for _, item := range lastLine {
  2036. if r, ok := item.(rune); ok {
  2037. lineW += rw.RuneWidth(r)
  2038. } else if att, ok := item.(*attachment.Attachment); ok {
  2039. lineW += uniseg.StringWidth(att.Display)
  2040. }
  2041. }
  2042. }
  2043. } else {
  2044. lines[len(lines)-1] = append(lines[len(lines)-1], word...)
  2045. lineW += wordW
  2046. }
  2047. }
  2048. }
  2049. if spaceW > 0 {
  2050. // There are trailing spaces. Add them.
  2051. for i := 0; i < spaceW; i++ {
  2052. lines[len(lines)-1] = append(lines[len(lines)-1], rune(' '))
  2053. lineW += 1
  2054. }
  2055. if lineW > width {
  2056. lines = append(lines, []any{})
  2057. }
  2058. }
  2059. return lines
  2060. }
  2061. func repeatSpaces(n int) []rune {
  2062. return []rune(strings.Repeat(string(' '), n))
  2063. }
  2064. // numDigits returns the number of digits in an integer.
  2065. func numDigits(n int) int {
  2066. if n == 0 {
  2067. return 1
  2068. }
  2069. count := 0
  2070. num := abs(n)
  2071. for num > 0 {
  2072. count++
  2073. num /= 10
  2074. }
  2075. return count
  2076. }
  2077. func clamp(v, low, high int) int {
  2078. if high < low {
  2079. low, high = high, low
  2080. }
  2081. return min(high, max(low, v))
  2082. }
  2083. func abs(n int) int {
  2084. if n < 0 {
  2085. return -n
  2086. }
  2087. return n
  2088. }