inputfield.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. package tview
  2. import (
  3. "math"
  4. "regexp"
  5. "strings"
  6. "sync"
  7. "unicode/utf8"
  8. "github.com/gdamore/tcell/v2"
  9. "github.com/rivo/uniseg"
  10. )
  11. const (
  12. AutocompletedNavigate = iota // The user navigated the autocomplete list (using the errow keys).
  13. AutocompletedTab // The user selected an autocomplete entry using the tab key.
  14. AutocompletedEnter // The user selected an autocomplete entry using the enter key.
  15. AutocompletedClick // The user selected an autocomplete entry by clicking the mouse button on it.
  16. )
  17. // InputField is a one-line box (three lines if there is a title) where the
  18. // user can enter text. Use [InputField.SetAcceptanceFunc] to accept or reject
  19. // input, [InputField.SetChangedFunc] to listen for changes, and
  20. // [InputField.SetMaskCharacter] to hide input from onlookers (e.g. for password
  21. // input).
  22. //
  23. // The input field also has an optional autocomplete feature. It is initialized
  24. // by the [InputField.SetAutocompleteFunc] function. For more control over the
  25. // autocomplete drop-down's behavior, you can also set the
  26. // [InputField.SetAutocompletedFunc].
  27. //
  28. // The following keys can be used for navigation and editing:
  29. //
  30. // - Left arrow: Move left by one character.
  31. // - Right arrow: Move right by one character.
  32. // - Down arrow: Open the autocomplete drop-down.
  33. // - Tab, Enter: Select the current autocomplete entry.
  34. // - Home, Ctrl-A, Alt-a: Move to the beginning of the line.
  35. // - End, Ctrl-E, Alt-e: Move to the end of the line.
  36. // - Alt-left, Alt-b: Move left by one word.
  37. // - Alt-right, Alt-f: Move right by one word.
  38. // - Backspace: Delete the character before the cursor.
  39. // - Delete: Delete the character after the cursor.
  40. // - Ctrl-K: Delete from the cursor to the end of the line.
  41. // - Ctrl-W: Delete the last word before the cursor.
  42. // - Ctrl-U: Delete the entire line.
  43. //
  44. // See https://github.com/rivo/tview/wiki/InputField for an example.
  45. type InputField struct {
  46. *Box
  47. // Whether or not this input field is disabled/read-only.
  48. disabled bool
  49. // The text that was entered.
  50. text string
  51. // The text to be displayed before the input area.
  52. label string
  53. // The text to be displayed in the input area when "text" is empty.
  54. placeholder string
  55. // The label style.
  56. labelStyle tcell.Style
  57. // The style of the input area with input text.
  58. fieldStyle tcell.Style
  59. // The style of the input area with placeholder text.
  60. placeholderStyle tcell.Style
  61. // The screen width of the label area. A value of 0 means use the width of
  62. // the label text.
  63. labelWidth int
  64. // The screen width of the input area. A value of 0 means extend as much as
  65. // possible.
  66. fieldWidth int
  67. // A character to mask entered text (useful for password fields). A value of 0
  68. // disables masking.
  69. maskCharacter rune
  70. // The cursor position as a byte index into the text string.
  71. cursorPos int
  72. // An optional autocomplete function which receives the current text of the
  73. // input field and returns a slice of strings to be displayed in a drop-down
  74. // selection.
  75. autocomplete func(text string) []string
  76. // The List object which shows the selectable autocomplete entries. If not
  77. // nil, the list's main texts represent the current autocomplete entries.
  78. autocompleteList *List
  79. autocompleteListMutex sync.Mutex
  80. // The styles of the autocomplete entries.
  81. autocompleteStyles struct {
  82. main tcell.Style
  83. selected tcell.Style
  84. background tcell.Color
  85. }
  86. // An optional function which is called when the user selects an
  87. // autocomplete entry. The text and index of the selected entry (within the
  88. // list) is provided, as well as the user action causing the selection (one
  89. // of the "Autocompleted" values). The function should return true if the
  90. // autocomplete list should be closed. If nil, the input field will be
  91. // updated automatically when the user navigates the autocomplete list.
  92. autocompleted func(text string, index int, source int) bool
  93. // An optional function which may reject the last character that was entered.
  94. accept func(text string, ch rune) bool
  95. // An optional function which is called when the input has changed.
  96. changed func(text string)
  97. // An optional function which is called when the user indicated that they
  98. // are done entering text. The key which was pressed is provided (tab,
  99. // shift-tab, enter, or escape).
  100. done func(tcell.Key)
  101. // A callback function set by the Form class and called when the user leaves
  102. // this form item.
  103. finished func(tcell.Key)
  104. fieldX int // The x-coordinate of the input field as determined during the last call to Draw().
  105. offset int // The number of bytes of the text string skipped ahead while drawing.
  106. }
  107. // NewInputField returns a new input field.
  108. func NewInputField() *InputField {
  109. i := &InputField{
  110. Box: NewBox(),
  111. labelStyle: tcell.StyleDefault.Foreground(Styles.SecondaryTextColor),
  112. fieldStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor),
  113. placeholderStyle: tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor),
  114. }
  115. i.autocompleteStyles.main = tcell.StyleDefault.Foreground(Styles.PrimitiveBackgroundColor)
  116. i.autocompleteStyles.selected = tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor)
  117. i.autocompleteStyles.background = Styles.MoreContrastBackgroundColor
  118. return i
  119. }
  120. // SetText sets the current text of the input field.
  121. func (i *InputField) SetText(text string) *InputField {
  122. i.text = text
  123. i.cursorPos = len(text)
  124. if i.changed != nil {
  125. i.changed(text)
  126. }
  127. return i
  128. }
  129. // GetText returns the current text of the input field.
  130. func (i *InputField) GetText() string {
  131. return i.text
  132. }
  133. // SetLabel sets the text to be displayed before the input area.
  134. func (i *InputField) SetLabel(label string) *InputField {
  135. i.label = label
  136. return i
  137. }
  138. // GetLabel returns the text to be displayed before the input area.
  139. func (i *InputField) GetLabel() string {
  140. return i.label
  141. }
  142. // SetLabelWidth sets the screen width of the label. A value of 0 will cause the
  143. // primitive to use the width of the label string.
  144. func (i *InputField) SetLabelWidth(width int) *InputField {
  145. i.labelWidth = width
  146. return i
  147. }
  148. // SetPlaceholder sets the text to be displayed when the input text is empty.
  149. func (i *InputField) SetPlaceholder(text string) *InputField {
  150. i.placeholder = text
  151. return i
  152. }
  153. // SetLabelColor sets the text color of the label.
  154. func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
  155. i.labelStyle = i.labelStyle.Foreground(color)
  156. return i
  157. }
  158. // SetLabelStyle sets the style of the label.
  159. func (i *InputField) SetLabelStyle(style tcell.Style) *InputField {
  160. i.labelStyle = style
  161. return i
  162. }
  163. // GetLabelStyle returns the style of the label.
  164. func (i *InputField) GetLabelStyle() tcell.Style {
  165. return i.labelStyle
  166. }
  167. // SetFieldBackgroundColor sets the background color of the input area.
  168. func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
  169. i.fieldStyle = i.fieldStyle.Background(color)
  170. return i
  171. }
  172. // SetFieldTextColor sets the text color of the input area.
  173. func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
  174. i.fieldStyle = i.fieldStyle.Foreground(color)
  175. return i
  176. }
  177. // SetFieldStyle sets the style of the input area (when no placeholder is
  178. // shown).
  179. func (i *InputField) SetFieldStyle(style tcell.Style) *InputField {
  180. i.fieldStyle = style
  181. return i
  182. }
  183. // GetFieldStyle returns the style of the input area (when no placeholder is
  184. // shown).
  185. func (i *InputField) GetFieldStyle() tcell.Style {
  186. return i.fieldStyle
  187. }
  188. // SetPlaceholderTextColor sets the text color of placeholder text.
  189. func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
  190. i.placeholderStyle = i.placeholderStyle.Foreground(color)
  191. return i
  192. }
  193. // SetPlaceholderStyle sets the style of the input area (when a placeholder is
  194. // shown).
  195. func (i *InputField) SetPlaceholderStyle(style tcell.Style) *InputField {
  196. i.placeholderStyle = style
  197. return i
  198. }
  199. // GetPlaceholderStyle returns the style of the input area (when a placeholder
  200. // is shown).
  201. func (i *InputField) GetPlaceholderStyle() tcell.Style {
  202. return i.placeholderStyle
  203. }
  204. // SetAutocompleteStyles sets the colors and style of the autocomplete entries.
  205. // For details, see List.SetMainTextStyle(), List.SetSelectedStyle(), and
  206. // Box.SetBackgroundColor().
  207. func (i *InputField) SetAutocompleteStyles(background tcell.Color, main, selected tcell.Style) *InputField {
  208. i.autocompleteStyles.background = background
  209. i.autocompleteStyles.main = main
  210. i.autocompleteStyles.selected = selected
  211. return i
  212. }
  213. // SetFormAttributes sets attributes shared by all form items.
  214. func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
  215. i.labelWidth = labelWidth
  216. i.backgroundColor = bgColor
  217. i.SetLabelColor(labelColor).
  218. SetFieldTextColor(fieldTextColor).
  219. SetFieldBackgroundColor(fieldBgColor)
  220. return i
  221. }
  222. // SetFieldWidth sets the screen width of the input area. A value of 0 means
  223. // extend as much as possible.
  224. func (i *InputField) SetFieldWidth(width int) *InputField {
  225. i.fieldWidth = width
  226. return i
  227. }
  228. // GetFieldWidth returns this primitive's field width.
  229. func (i *InputField) GetFieldWidth() int {
  230. return i.fieldWidth
  231. }
  232. // GetFieldHeight returns this primitive's field height.
  233. func (i *InputField) GetFieldHeight() int {
  234. return 1
  235. }
  236. // SetDisabled sets whether or not the item is disabled / read-only.
  237. func (i *InputField) SetDisabled(disabled bool) FormItem {
  238. i.disabled = disabled
  239. if i.finished != nil {
  240. i.finished(-1)
  241. }
  242. return i
  243. }
  244. // SetMaskCharacter sets a character that masks user input on a screen. A value
  245. // of 0 disables masking.
  246. func (i *InputField) SetMaskCharacter(mask rune) *InputField {
  247. i.maskCharacter = mask
  248. return i
  249. }
  250. // SetAutocompleteFunc sets an autocomplete callback function which may return
  251. // strings to be selected from a drop-down based on the current text of the
  252. // input field. The drop-down appears only if len(entries) > 0. The callback is
  253. // invoked in this function and whenever the current text changes or when
  254. // Autocomplete() is called. Entries are cleared when the user selects an entry
  255. // or presses Escape.
  256. func (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entries []string)) *InputField {
  257. i.autocomplete = callback
  258. i.Autocomplete()
  259. return i
  260. }
  261. // SetAutocompletedFunc sets a callback function which is invoked when the user
  262. // selects an entry from the autocomplete drop-down list. The function is passed
  263. // the text of the selected entry (stripped of any color tags), the index of the
  264. // entry, and the user action that caused the selection, e.g.
  265. // [AutocompletedNavigate]. It returns true if the autocomplete drop-down should
  266. // be closed after the callback returns or false if it should remain open, in
  267. // which case [InputField.Autocomplete] is called to update the drop-down's
  268. // contents.
  269. //
  270. // If no such callback is set (or nil is provided), the input field will be
  271. // updated with the selection any time the user navigates the autocomplete
  272. // drop-down list. So this function essentially gives you more control over the
  273. // autocomplete functionality.
  274. func (i *InputField) SetAutocompletedFunc(autocompleted func(text string, index int, source int) bool) *InputField {
  275. i.autocompleted = autocompleted
  276. return i
  277. }
  278. // Autocomplete invokes the autocomplete callback (if there is one). If the
  279. // length of the returned autocomplete entries slice is greater than 0, the
  280. // input field will present the user with a corresponding drop-down list the
  281. // next time the input field is drawn.
  282. //
  283. // It is safe to call this function from any goroutine. Note that the input
  284. // field is not redrawn automatically unless called from the main goroutine
  285. // (e.g. in response to events).
  286. func (i *InputField) Autocomplete() *InputField {
  287. i.autocompleteListMutex.Lock()
  288. defer i.autocompleteListMutex.Unlock()
  289. if i.autocomplete == nil {
  290. return i
  291. }
  292. // Do we have any autocomplete entries?
  293. entries := i.autocomplete(i.text)
  294. if len(entries) == 0 {
  295. // No entries, no list.
  296. i.autocompleteList = nil
  297. return i
  298. }
  299. // Make a list if we have none.
  300. if i.autocompleteList == nil {
  301. i.autocompleteList = NewList()
  302. i.autocompleteList.ShowSecondaryText(false).
  303. SetMainTextStyle(i.autocompleteStyles.main).
  304. SetSelectedStyle(i.autocompleteStyles.selected).
  305. SetHighlightFullLine(true).
  306. SetBackgroundColor(i.autocompleteStyles.background)
  307. }
  308. // Fill it with the entries.
  309. currentEntry := -1
  310. suffixLength := 9999 // I'm just waiting for the day somebody opens an issue with this number being too small.
  311. i.autocompleteList.Clear()
  312. for index, entry := range entries {
  313. i.autocompleteList.AddItem(entry, "", 0, nil)
  314. if strings.HasPrefix(entry, i.text) && len(entry)-len(i.text) < suffixLength {
  315. currentEntry = index
  316. suffixLength = len(i.text) - len(entry)
  317. }
  318. }
  319. // Set the selection if we have one.
  320. if currentEntry >= 0 {
  321. i.autocompleteList.SetCurrentItem(currentEntry)
  322. }
  323. return i
  324. }
  325. // SetAcceptanceFunc sets a handler which may reject the last character that was
  326. // entered (by returning false).
  327. //
  328. // This package defines a number of variables prefixed with InputField which may
  329. // be used for common input (e.g. numbers, maximum text length).
  330. func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
  331. i.accept = handler
  332. return i
  333. }
  334. // SetChangedFunc sets a handler which is called whenever the text of the input
  335. // field has changed. It receives the current text (after the change).
  336. func (i *InputField) SetChangedFunc(handler func(text string)) *InputField {
  337. i.changed = handler
  338. return i
  339. }
  340. // SetDoneFunc sets a handler which is called when the user is done entering
  341. // text. The callback function is provided with the key that was pressed, which
  342. // is one of the following:
  343. //
  344. // - KeyEnter: Done entering text.
  345. // - KeyEscape: Abort text input.
  346. // - KeyTab: Move to the next field.
  347. // - KeyBacktab: Move to the previous field.
  348. func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {
  349. i.done = handler
  350. return i
  351. }
  352. // SetFinishedFunc sets a callback invoked when the user leaves this form item.
  353. func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
  354. i.finished = handler
  355. return i
  356. }
  357. // Focus is called when this primitive receives focus.
  358. func (i *InputField) Focus(delegate func(p Primitive)) {
  359. // If we're part of a form and this item is disabled, there's nothing the
  360. // user can do here so we're finished.
  361. if i.finished != nil && i.disabled {
  362. i.finished(-1)
  363. return
  364. }
  365. i.Box.Focus(delegate)
  366. }
  367. // Blur is called when this primitive loses focus.
  368. func (i *InputField) Blur() {
  369. i.Box.Blur()
  370. i.autocompleteList = nil // Hide the autocomplete drop-down.
  371. }
  372. // Draw draws this primitive onto the screen.
  373. func (i *InputField) Draw(screen tcell.Screen) {
  374. i.Box.DrawForSubclass(screen, i)
  375. // Prepare
  376. x, y, width, height := i.GetInnerRect()
  377. rightLimit := x + width
  378. if height < 1 || rightLimit <= x {
  379. return
  380. }
  381. // Draw label.
  382. _, labelBg, _ := i.labelStyle.Decompose()
  383. if i.labelWidth > 0 {
  384. labelWidth := i.labelWidth
  385. if labelWidth > width {
  386. labelWidth = width
  387. }
  388. printWithStyle(screen, i.label, x, y, 0, labelWidth, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
  389. x += labelWidth
  390. } else {
  391. _, drawnWidth, _, _ := printWithStyle(screen, i.label, x, y, 0, width, AlignLeft, i.labelStyle, labelBg == tcell.ColorDefault)
  392. x += drawnWidth
  393. }
  394. // Draw input area.
  395. i.fieldX = x
  396. fieldWidth := i.fieldWidth
  397. text := i.text
  398. inputStyle := i.fieldStyle
  399. placeholder := text == "" && i.placeholder != ""
  400. if placeholder {
  401. inputStyle = i.placeholderStyle
  402. }
  403. _, inputBg, _ := inputStyle.Decompose()
  404. if fieldWidth == 0 {
  405. fieldWidth = math.MaxInt32
  406. }
  407. if rightLimit-x < fieldWidth {
  408. fieldWidth = rightLimit - x
  409. }
  410. if i.disabled {
  411. inputStyle = inputStyle.Background(i.backgroundColor)
  412. }
  413. if inputBg != tcell.ColorDefault {
  414. for index := 0; index < fieldWidth; index++ {
  415. screen.SetContent(x+index, y, ' ', nil, inputStyle)
  416. }
  417. }
  418. // Text.
  419. var cursorScreenPos int
  420. if placeholder {
  421. // Draw placeholder text.
  422. printWithStyle(screen, Escape(i.placeholder), x, y, 0, fieldWidth, AlignLeft, i.placeholderStyle, true)
  423. i.offset = 0
  424. } else {
  425. // Draw entered text.
  426. if i.maskCharacter > 0 {
  427. text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
  428. }
  429. if fieldWidth >= uniseg.StringWidth(text) {
  430. // We have enough space for the full text.
  431. printWithStyle(screen, Escape(text), x, y, 0, fieldWidth, AlignLeft, i.fieldStyle, true)
  432. i.offset = 0
  433. iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth, boundaries int) bool {
  434. if textPos >= i.cursorPos {
  435. return true
  436. }
  437. cursorScreenPos += screenWidth
  438. return false
  439. })
  440. } else {
  441. // The text doesn't fit. Where is the cursor?
  442. if i.cursorPos < 0 {
  443. i.cursorPos = 0
  444. } else if i.cursorPos > len(text) {
  445. i.cursorPos = len(text)
  446. }
  447. // Shift the text so the cursor is inside the field.
  448. var shiftLeft int
  449. if i.offset > i.cursorPos {
  450. i.offset = i.cursorPos
  451. } else if subWidth := uniseg.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
  452. shiftLeft = subWidth - fieldWidth + 1
  453. }
  454. currentOffset := i.offset
  455. iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth, boundaries int) bool {
  456. if textPos >= currentOffset {
  457. if shiftLeft > 0 {
  458. i.offset = textPos + textWidth
  459. shiftLeft -= screenWidth
  460. } else {
  461. if textPos+textWidth > i.cursorPos {
  462. return true
  463. }
  464. cursorScreenPos += screenWidth
  465. }
  466. }
  467. return false
  468. })
  469. printWithStyle(screen, Escape(text[i.offset:]), x, y, 0, fieldWidth, AlignLeft, i.fieldStyle, true)
  470. }
  471. }
  472. // Draw autocomplete list.
  473. i.autocompleteListMutex.Lock()
  474. defer i.autocompleteListMutex.Unlock()
  475. if i.autocompleteList != nil {
  476. // How much space do we need?
  477. lheight := i.autocompleteList.GetItemCount()
  478. lwidth := 0
  479. for index := 0; index < lheight; index++ {
  480. entry, _ := i.autocompleteList.GetItemText(index)
  481. width := TaggedStringWidth(entry)
  482. if width > lwidth {
  483. lwidth = width
  484. }
  485. }
  486. // We prefer to drop down but if there is no space, maybe drop up?
  487. lx := x
  488. ly := y + 1
  489. _, sheight := screen.Size()
  490. if ly+lheight >= sheight && ly-2 > lheight-ly {
  491. ly = y - lheight
  492. if ly < 0 {
  493. ly = 0
  494. }
  495. }
  496. if ly+lheight >= sheight {
  497. lheight = sheight - ly
  498. }
  499. i.autocompleteList.SetRect(lx, ly, lwidth, lheight)
  500. i.autocompleteList.Draw(screen)
  501. }
  502. // Set cursor.
  503. if i.HasFocus() {
  504. screen.ShowCursor(x+cursorScreenPos, y)
  505. }
  506. }
  507. // InputHandler returns the handler for this primitive.
  508. func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
  509. return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
  510. if i.disabled {
  511. return
  512. }
  513. // Trigger changed events.
  514. currentText := i.text
  515. defer func() {
  516. if i.text != currentText {
  517. i.Autocomplete()
  518. if i.changed != nil {
  519. i.changed(i.text)
  520. }
  521. }
  522. }()
  523. // Movement functions.
  524. home := func() { i.cursorPos = 0 }
  525. end := func() { i.cursorPos = len(i.text) }
  526. moveLeft := func() {
  527. iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
  528. i.cursorPos -= textWidth
  529. return true
  530. })
  531. }
  532. moveRight := func() {
  533. iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth, boundaries int) bool {
  534. i.cursorPos += textWidth
  535. return true
  536. })
  537. }
  538. moveWordLeft := func() {
  539. i.cursorPos = len(regexp.MustCompile(`\S+\s*$`).ReplaceAllString(i.text[:i.cursorPos], ""))
  540. }
  541. moveWordRight := func() {
  542. i.cursorPos = len(i.text) - len(regexp.MustCompile(`^\s*\S+\s*`).ReplaceAllString(i.text[i.cursorPos:], ""))
  543. }
  544. // Add character function. Returns whether or not the rune character is
  545. // accepted.
  546. add := func(r rune) bool {
  547. newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
  548. if i.accept != nil && !i.accept(newText, r) {
  549. return false
  550. }
  551. i.text = newText
  552. i.cursorPos += len(string(r))
  553. return true
  554. }
  555. // Finish up.
  556. finish := func(key tcell.Key) {
  557. if i.done != nil {
  558. i.done(key)
  559. }
  560. if i.finished != nil {
  561. i.finished(key)
  562. }
  563. }
  564. // If we have an autocomplete list, there are certain keys we will
  565. // forward to it.
  566. i.autocompleteListMutex.Lock()
  567. defer i.autocompleteListMutex.Unlock()
  568. if i.autocompleteList != nil {
  569. i.autocompleteList.SetChangedFunc(nil)
  570. switch key := event.Key(); key {
  571. case tcell.KeyEscape: // Close the list.
  572. i.autocompleteList = nil
  573. return
  574. case tcell.KeyEnter, tcell.KeyTab: // Intentional selection.
  575. if i.autocompleted != nil {
  576. index := i.autocompleteList.GetCurrentItem()
  577. text, _ := i.autocompleteList.GetItemText(index)
  578. source := AutocompletedEnter
  579. if key == tcell.KeyTab {
  580. source = AutocompletedTab
  581. }
  582. if i.autocompleted(stripTags(text), index, source) {
  583. i.autocompleteList = nil
  584. currentText = i.GetText()
  585. }
  586. } else {
  587. i.autocompleteList = nil
  588. }
  589. return
  590. case tcell.KeyDown, tcell.KeyUp, tcell.KeyPgDn, tcell.KeyPgUp:
  591. i.autocompleteList.SetChangedFunc(func(index int, text, secondaryText string, shortcut rune) {
  592. text = stripTags(text)
  593. if i.autocompleted != nil {
  594. if i.autocompleted(text, index, AutocompletedNavigate) {
  595. i.autocompleteList = nil
  596. currentText = i.GetText()
  597. }
  598. } else {
  599. i.SetText(text)
  600. currentText = stripTags(text) // We want to keep the autocomplete list open and unchanged.
  601. }
  602. })
  603. i.autocompleteList.InputHandler()(event, setFocus)
  604. return
  605. }
  606. }
  607. // Process key event for the input field.
  608. switch key := event.Key(); key {
  609. case tcell.KeyRune: // Regular character.
  610. if event.Modifiers()&tcell.ModAlt > 0 {
  611. // We accept some Alt- key combinations.
  612. switch event.Rune() {
  613. case 'a': // Home.
  614. home()
  615. case 'e': // End.
  616. end()
  617. case 'b': // Move word left.
  618. moveWordLeft()
  619. case 'f': // Move word right.
  620. moveWordRight()
  621. default:
  622. if !add(event.Rune()) {
  623. return
  624. }
  625. }
  626. } else {
  627. // Other keys are simply accepted as regular characters.
  628. if !add(event.Rune()) {
  629. return
  630. }
  631. }
  632. case tcell.KeyCtrlU: // Delete all.
  633. i.text = ""
  634. i.cursorPos = 0
  635. case tcell.KeyCtrlK: // Delete until the end of the line.
  636. i.text = i.text[:i.cursorPos]
  637. case tcell.KeyCtrlW: // Delete last word.
  638. lastWord := regexp.MustCompile(`\S+\s*$`)
  639. newText := lastWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
  640. i.cursorPos -= len(i.text) - len(newText)
  641. i.text = newText
  642. case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
  643. iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
  644. i.text = i.text[:textPos] + i.text[textPos+textWidth:]
  645. i.cursorPos -= textWidth
  646. return true
  647. })
  648. if i.offset >= i.cursorPos {
  649. i.offset = 0
  650. }
  651. case tcell.KeyDelete, tcell.KeyCtrlD: // Delete character after the cursor.
  652. iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth, boundaries int) bool {
  653. i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
  654. return true
  655. })
  656. case tcell.KeyLeft:
  657. if event.Modifiers()&tcell.ModAlt > 0 {
  658. moveWordLeft()
  659. } else {
  660. moveLeft()
  661. }
  662. case tcell.KeyCtrlB:
  663. moveLeft()
  664. case tcell.KeyRight:
  665. if event.Modifiers()&tcell.ModAlt > 0 {
  666. moveWordRight()
  667. } else {
  668. moveRight()
  669. }
  670. case tcell.KeyCtrlF:
  671. moveRight()
  672. case tcell.KeyHome, tcell.KeyCtrlA:
  673. home()
  674. case tcell.KeyEnd, tcell.KeyCtrlE:
  675. end()
  676. case tcell.KeyDown:
  677. i.autocompleteListMutex.Unlock() // We're still holding a lock.
  678. i.Autocomplete()
  679. i.autocompleteListMutex.Lock()
  680. case tcell.KeyEnter, tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:
  681. finish(key)
  682. }
  683. })
  684. }
  685. // MouseHandler returns the mouse handler for this primitive.
  686. func (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  687. return i.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
  688. if i.disabled {
  689. return false, nil
  690. }
  691. currentText := i.GetText()
  692. defer func() {
  693. if i.GetText() != currentText {
  694. i.Autocomplete()
  695. if i.changed != nil {
  696. i.changed(i.text)
  697. }
  698. }
  699. }()
  700. // If we have an autocomplete list, forward the mouse event to it.
  701. i.autocompleteListMutex.Lock()
  702. defer i.autocompleteListMutex.Unlock()
  703. if i.autocompleteList != nil {
  704. i.autocompleteList.SetChangedFunc(func(index int, text, secondaryText string, shortcut rune) {
  705. text = stripTags(text)
  706. if i.autocompleted != nil {
  707. if i.autocompleted(text, index, AutocompletedClick) {
  708. i.autocompleteList = nil
  709. currentText = i.GetText()
  710. }
  711. return
  712. }
  713. i.SetText(text)
  714. i.autocompleteList = nil
  715. })
  716. if consumed, _ = i.autocompleteList.MouseHandler()(action, event, setFocus); consumed {
  717. setFocus(i)
  718. return
  719. }
  720. }
  721. // Is mouse event within the input field?
  722. x, y := event.Position()
  723. _, rectY, _, _ := i.GetInnerRect()
  724. if !i.InRect(x, y) {
  725. return false, nil
  726. }
  727. // Process mouse event.
  728. if y == rectY {
  729. if action == MouseLeftDown {
  730. setFocus(i)
  731. consumed = true
  732. } else if action == MouseLeftClick {
  733. // Determine where to place the cursor.
  734. if x >= i.fieldX {
  735. if !iterateString(i.text[i.offset:], func(main rune, comb []rune, textPos int, textWidth int, screenPos int, screenWidth, boundaries int) bool {
  736. if x-i.fieldX < screenPos+screenWidth {
  737. i.cursorPos = textPos + i.offset
  738. return true
  739. }
  740. return false
  741. }) {
  742. i.cursorPos = len(i.text)
  743. }
  744. }
  745. consumed = true
  746. }
  747. }
  748. return
  749. })
  750. }