list.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. package choose_fonts
  2. import (
  3. "fmt"
  4. "strings"
  5. "sync"
  6. "kitty/tools/tui/loop"
  7. "kitty/tools/tui/readline"
  8. "kitty/tools/utils"
  9. "kitty/tools/utils/style"
  10. "kitty/tools/wcswidth"
  11. )
  12. var _ = fmt.Print
  13. type preview_cache_key struct {
  14. family string
  15. width, height int
  16. }
  17. type preview_cache_value struct {
  18. path string
  19. width, height int
  20. }
  21. type FontList struct {
  22. rl *readline.Readline
  23. family_list FamilyList
  24. fonts map[string][]ListedFont
  25. family_list_updated bool
  26. resolved_faces_from_kitty_conf ResolvedFaces
  27. handler *handler
  28. variable_data_requested_for *utils.Set[string]
  29. preview_cache map[preview_cache_key]preview_cache_value
  30. preview_cache_mutex sync.Mutex
  31. }
  32. func (self *FontList) initialize(h *handler) error {
  33. self.handler = h
  34. self.preview_cache = make(map[preview_cache_key]preview_cache_value)
  35. self.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Family: "})
  36. self.variable_data_requested_for = utils.NewSet[string](256)
  37. return nil
  38. }
  39. func (self *FontList) draw_search_bar() {
  40. lp := self.handler.lp
  41. lp.SetCursorVisible(true)
  42. lp.SetCursorShape(loop.BAR_CURSOR, true)
  43. sz, err := lp.ScreenSize()
  44. if err != nil {
  45. return
  46. }
  47. lp.MoveCursorTo(1, int(sz.HeightCells))
  48. lp.ClearToEndOfLine()
  49. self.rl.RedrawNonAtomic()
  50. }
  51. const SEPARATOR = "║"
  52. func center_string(x string, width int, filler ...string) string {
  53. space := " "
  54. if len(filler) > 0 {
  55. space = filler[0]
  56. }
  57. l := wcswidth.Stringwidth(x)
  58. spaces := int(float64(width-l) / 2)
  59. space = strings.Repeat(space, utils.Max(0, spaces))
  60. return space + x + space
  61. }
  62. func (self *handler) format_title(title string, start_x int) string {
  63. sz, _ := self.lp.ScreenSize()
  64. return self.lp.SprintStyled("fg=green bold", center_string(title, int(sz.WidthCells)-start_x))
  65. }
  66. func (self *FontList) draw_family_summary(start_x int, sz loop.ScreenSize) (err error) {
  67. lp := self.handler.lp
  68. family := self.family_list.CurrentFamily()
  69. if family == "" || int(sz.WidthCells) < start_x+2 {
  70. return nil
  71. }
  72. lines := []string{self.handler.format_title(family, start_x), ""}
  73. width := int(sz.WidthCells) - start_x - 1
  74. add_line := func(x string) {
  75. lines = append(lines, style.WrapTextAsLines(x, width, style.WrapOptions{})...)
  76. }
  77. fonts := self.fonts[family]
  78. if len(fonts) == 0 {
  79. return fmt.Errorf("The family: %s has no fonts", family)
  80. }
  81. if has_variable_data_for_font(fonts[0]) {
  82. s := styles_in_family(family, fonts)
  83. for _, sg := range s.style_groups {
  84. styles := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(sg.styles, ", ")
  85. add_line(styles)
  86. add_line("")
  87. }
  88. if s.has_variable_faces {
  89. add_line(fmt.Sprintf("This font is %s allowing for finer style control", lp.SprintStyled("fg=magenta", "variable")))
  90. }
  91. add_line(fmt.Sprintf("Press the %s key to choose this family", lp.SprintStyled("fg=yellow", "Enter")))
  92. } else {
  93. lines = append(lines, "Reading font data, please wait…")
  94. key := fonts[0].cache_key()
  95. if !self.variable_data_requested_for.Has(key) {
  96. self.variable_data_requested_for.Add(key)
  97. go func() {
  98. self.handler.set_worker_error(ensure_variable_data_for_fonts(fonts...))
  99. lp.WakeupMainThread()
  100. }()
  101. }
  102. }
  103. y := 0
  104. for _, line := range lines {
  105. if y >= int(sz.HeightCells)-1 {
  106. break
  107. }
  108. lp.MoveCursorTo(start_x+1, y+1)
  109. lp.QueueWriteString(line)
  110. y++
  111. }
  112. if self.handler.text_style.Background != "" {
  113. return self.draw_preview(start_x, y, sz)
  114. }
  115. return
  116. }
  117. func (self *FontList) draw_preview(x, y int, sz loop.ScreenSize) (err error) {
  118. width_cells, height_cells := int(sz.WidthCells)-x, int(sz.HeightCells)-y
  119. if height_cells < 3 {
  120. return
  121. }
  122. y++
  123. self.handler.lp.MoveCursorTo(x+1, y+1)
  124. self.handler.draw_preview_header(x)
  125. y++
  126. height_cells -= 2
  127. self.handler.lp.MoveCursorTo(x+1, y+1)
  128. key := preview_cache_key{
  129. family: self.family_list.CurrentFamily(), width: int(sz.CellWidth) * width_cells, height: int(sz.CellHeight) * height_cells,
  130. }
  131. if key.family == "" {
  132. return
  133. }
  134. self.preview_cache_mutex.Lock()
  135. defer self.preview_cache_mutex.Unlock()
  136. cc := self.preview_cache[key]
  137. switch cc.path {
  138. case "":
  139. self.preview_cache[key] = preview_cache_value{path: "requested"}
  140. go func() {
  141. var r map[string]RenderedSampleTransmit
  142. self.handler.set_worker_error(kitty_font_backend.query("render_family_samples", map[string]any{
  143. "text_style": self.handler.text_style, "font_family": key.family, "width": key.width, "height": key.height,
  144. "output_dir": self.handler.temp_dir,
  145. }, &r))
  146. self.preview_cache_mutex.Lock()
  147. defer self.preview_cache_mutex.Unlock()
  148. self.preview_cache[key] = preview_cache_value{path: r["font_family"].Path, width: r["font_family"].Canvas_width, height: r["font_family"].Canvas_height}
  149. self.handler.lp.WakeupMainThread()
  150. }()
  151. return
  152. case "requested":
  153. return
  154. }
  155. self.handler.graphics_manager.display_image(0, cc.path, cc.width, cc.height)
  156. return
  157. }
  158. func (self *FontList) on_wakeup() error {
  159. if !self.family_list_updated {
  160. self.family_list_updated = true
  161. self.family_list.UpdateFamilies(utils.StableSortWithKey(utils.Keys(self.fonts), strings.ToLower))
  162. self.family_list.SelectFamily(self.resolved_faces_from_kitty_conf.Font_family.Family)
  163. }
  164. return self.handler.draw_screen()
  165. }
  166. func (self *FontList) draw_screen() (err error) {
  167. lp := self.handler.lp
  168. sz, err := lp.ScreenSize()
  169. if err != nil {
  170. return err
  171. }
  172. num_rows := max(0, int(sz.HeightCells)-1)
  173. mw := self.family_list.max_width + 1
  174. green_fg, _, _ := strings.Cut(lp.SprintStyled("fg=green", "|"), "|")
  175. lines := make([]string, 0, num_rows)
  176. for _, l := range self.family_list.Lines(num_rows) {
  177. line := l.text
  178. if l.is_current {
  179. line = strings.ReplaceAll(line, MARK_AFTER, green_fg)
  180. line = lp.SprintStyled("fg=green", ">") + lp.SprintStyled("fg=green bold", line)
  181. } else {
  182. line = " " + line
  183. }
  184. lines = append(lines, line)
  185. }
  186. _, _, str := self.handler.render_lines.InRectangle(lines, 0, 0, 0, num_rows, &self.handler.mouse_state, self.on_click)
  187. lp.QueueWriteString(str)
  188. seps := strings.Repeat(SEPARATOR, num_rows)
  189. seps = strings.TrimSpace(seps)
  190. _, _, str = self.handler.render_lines.InRectangle(strings.Split(seps, ""), mw+1, 0, 0, num_rows, &self.handler.mouse_state)
  191. lp.QueueWriteString(str)
  192. if self.family_list.Len() > 0 {
  193. if err = self.draw_family_summary(mw+3, sz); err != nil {
  194. return err
  195. }
  196. }
  197. self.draw_search_bar()
  198. return
  199. }
  200. func (self *FontList) on_click(id string) error {
  201. which, data, found := strings.Cut(id, ":")
  202. if !found {
  203. return fmt.Errorf("Not a valid click id: %s", id)
  204. }
  205. switch which {
  206. case "family-chosen":
  207. if self.handler.state == LISTING_FAMILIES {
  208. if self.family_list.Select(data) {
  209. self.handler.draw_screen()
  210. } else {
  211. self.handler.lp.Beep()
  212. }
  213. }
  214. }
  215. return nil
  216. }
  217. func (self *FontList) update_family_search() {
  218. text := self.rl.AllText()
  219. if self.family_list.UpdateSearch(text) {
  220. self.handler.draw_screen()
  221. } else {
  222. self.draw_search_bar()
  223. }
  224. }
  225. func (self *FontList) next(delta int, allow_wrapping bool) error {
  226. if self.family_list.Next(delta, allow_wrapping) {
  227. return self.handler.draw_screen()
  228. }
  229. self.handler.lp.Beep()
  230. return nil
  231. }
  232. func (self *FontList) on_key_event(event *loop.KeyEvent) (err error) {
  233. if event.MatchesPressOrRepeat("enter") {
  234. event.Handled = true
  235. if family := self.family_list.CurrentFamily(); family != "" {
  236. return self.handler.faces.on_enter(family)
  237. }
  238. self.handler.lp.Beep()
  239. return
  240. }
  241. if event.MatchesPressOrRepeat("esc") {
  242. event.Handled = true
  243. if self.rl.AllText() != "" {
  244. self.rl.ResetText()
  245. self.update_family_search()
  246. self.handler.draw_screen()
  247. } else {
  248. return fmt.Errorf("canceled by user")
  249. }
  250. return
  251. }
  252. ev := event
  253. if ev.MatchesPressOrRepeat("down") {
  254. ev.Handled = true
  255. return self.next(1, true)
  256. }
  257. if ev.MatchesPressOrRepeat("up") {
  258. ev.Handled = true
  259. return self.next(-1, true)
  260. }
  261. if ev.MatchesPressOrRepeat("page_down") {
  262. ev.Handled = true
  263. sz, err := self.handler.lp.ScreenSize()
  264. if err == nil {
  265. err = self.next(int(sz.HeightCells)-3, false)
  266. }
  267. return err
  268. }
  269. if ev.MatchesPressOrRepeat("page_up") {
  270. ev.Handled = true
  271. sz, err := self.handler.lp.ScreenSize()
  272. if err == nil {
  273. err = self.next(3-int(sz.HeightCells), false)
  274. }
  275. return err
  276. }
  277. if err = self.rl.OnKeyEvent(event); err != nil {
  278. if err == readline.ErrAcceptInput {
  279. return nil
  280. }
  281. return err
  282. }
  283. if event.Handled {
  284. self.update_family_search()
  285. }
  286. self.draw_search_bar()
  287. return
  288. }
  289. func (self *FontList) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) {
  290. if err = self.rl.OnText(text, from_key_event, in_bracketed_paste); err != nil {
  291. return err
  292. }
  293. self.update_family_search()
  294. return
  295. }