indent-and-wrap.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package style
  3. import (
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. "unicode"
  8. "unicode/utf8"
  9. "kitty/tools/utils"
  10. "kitty/tools/wcswidth"
  11. )
  12. type sgr_color struct {
  13. number int
  14. color RGBA
  15. }
  16. func (self sgr_color) as_sgr(base int) string {
  17. if self.number == 0 {
  18. return ""
  19. }
  20. if self.number > 0 {
  21. n := self.number - 1
  22. if n <= 15 && (base == 30 || base == 40) {
  23. if n <= 7 {
  24. return strconv.Itoa(base + n)
  25. }
  26. return strconv.Itoa(base + 60 + n - 7)
  27. }
  28. return fmt.Sprintf("%d:5:%d", base+8, n)
  29. }
  30. return fmt.Sprintf("%d:2:%d:%d:%d", base+8, self.color.Red, self.color.Green, self.color.Blue)
  31. }
  32. func (self *sgr_color) from_extended(nums []int) bool {
  33. switch nums[0] {
  34. case 5:
  35. if len(nums) > 1 {
  36. self.number = 1 + nums[1]
  37. return true
  38. }
  39. case 2:
  40. if len(nums) > 3 {
  41. self.number = -1
  42. self.color.Red = uint8(nums[1])
  43. self.color.Green = uint8(nums[2])
  44. self.color.Blue = uint8(nums[3])
  45. return true
  46. }
  47. }
  48. return false
  49. }
  50. type sgr_state struct {
  51. italic, reverse, bold, dim, strikethrough bool
  52. underline_style underline_style
  53. fg, bg, uc sgr_color
  54. }
  55. func (self *sgr_state) reset() {
  56. *self = sgr_state{}
  57. }
  58. func (self sgr_state) as_sgr(for_close bool) string {
  59. ans := make([]byte, 0, 32)
  60. if for_close {
  61. if self.bold {
  62. ans = append(ans, "221;"...)
  63. }
  64. if self.dim {
  65. ans = append(ans, "222;"...)
  66. }
  67. if self.italic {
  68. ans = append(ans, "23;"...)
  69. }
  70. if self.reverse {
  71. ans = append(ans, "27;"...)
  72. }
  73. if self.strikethrough {
  74. ans = append(ans, "29;"...)
  75. }
  76. if self.underline_style != no_underline && self.underline_style != nil_underline {
  77. ans = append(ans, "4:0;"...)
  78. }
  79. if self.fg.number != 0 {
  80. ans = append(ans, "39;"...)
  81. }
  82. if self.bg.number != 0 {
  83. ans = append(ans, "49;"...)
  84. }
  85. if self.uc.number != 0 {
  86. ans = append(ans, "59;"...)
  87. }
  88. } else {
  89. if self.bold {
  90. ans = append(ans, "1;"...)
  91. }
  92. if self.dim {
  93. ans = append(ans, "2;"...)
  94. }
  95. if self.italic {
  96. ans = append(ans, "3;"...)
  97. }
  98. if self.reverse {
  99. ans = append(ans, "7;"...)
  100. }
  101. if self.strikethrough {
  102. ans = append(ans, "9;"...)
  103. }
  104. if self.underline_style != no_underline && self.underline_style != nil_underline {
  105. ans = append(ans, fmt.Sprintf("4:%d;", self.underline_style)...)
  106. }
  107. if q := self.fg.as_sgr(30); q != "" {
  108. ans = append(ans, q...)
  109. ans = append(ans, ';')
  110. }
  111. if q := self.bg.as_sgr(40); q != "" {
  112. ans = append(ans, q...)
  113. ans = append(ans, ';')
  114. }
  115. if q := self.uc.as_sgr(50); q != "" {
  116. ans = append(ans, q...)
  117. ans = append(ans, ';')
  118. }
  119. }
  120. if len(ans) > 0 {
  121. ans = ans[:len(ans)-1]
  122. }
  123. return utils.UnsafeBytesToString(ans)
  124. }
  125. func (self sgr_state) as_escape_codes(for_close bool) string {
  126. q := self.as_sgr(for_close)
  127. if q == "" {
  128. return q
  129. }
  130. return fmt.Sprintf("\x1b[%sm", q)
  131. }
  132. func (self *sgr_state) apply_csi(raw string) {
  133. if !strings.HasSuffix(raw, "m") {
  134. return
  135. }
  136. raw = raw[:len(raw)-1]
  137. if raw == "" {
  138. raw = "0"
  139. }
  140. parts := strings.Split(raw, ";")
  141. nums := make([]int, 0, 8)
  142. for _, part := range parts {
  143. subparts := strings.Split(part, ":")
  144. nums = nums[:0]
  145. for _, b := range subparts {
  146. q, err := strconv.Atoi(b)
  147. if err == nil {
  148. nums = append(nums, q)
  149. }
  150. }
  151. if len(nums) == 0 {
  152. continue
  153. }
  154. switch nums[0] {
  155. case 0:
  156. self.reset()
  157. case 1:
  158. self.dim, self.bold = false, true
  159. case 221:
  160. self.bold = false
  161. case 2:
  162. self.dim, self.bold = true, false
  163. case 222:
  164. self.dim = false
  165. case 22:
  166. self.dim, self.bold = false, false
  167. case 3:
  168. self.italic = true
  169. case 23:
  170. self.italic = false
  171. case 7:
  172. self.reverse = true
  173. case 27:
  174. self.reverse = false
  175. case 9:
  176. self.strikethrough = true
  177. case 29:
  178. self.strikethrough = false
  179. case 24:
  180. self.underline_style = no_underline
  181. case 4:
  182. us := 1
  183. if len(nums) > 1 {
  184. us = nums[1]
  185. }
  186. switch us {
  187. case 0:
  188. self.underline_style = no_underline
  189. case 1:
  190. self.underline_style = straight_underline
  191. case 2:
  192. self.underline_style = double_underline
  193. case 3:
  194. self.underline_style = curly_underline
  195. case 4:
  196. self.underline_style = dotted_underline
  197. case 5:
  198. self.underline_style = dashed_underline
  199. }
  200. case 30, 31, 32, 33, 34, 35, 36, 37:
  201. self.fg.number = nums[0] + 1 - 30
  202. case 90, 91, 92, 93, 94, 95, 96, 97:
  203. self.fg.number = nums[0] + 1 - 82
  204. case 38:
  205. self.fg.from_extended(nums[1:])
  206. case 39:
  207. self.fg.number = 0
  208. case 40, 41, 42, 43, 44, 45, 46, 47:
  209. self.bg.number = nums[0] + 1 - 40
  210. case 100, 101, 102, 103, 104, 105, 106, 107:
  211. self.bg.number = nums[0] + 1 - 92
  212. case 48:
  213. self.bg.from_extended(nums[1:])
  214. case 49:
  215. self.bg.number = 0
  216. case 58:
  217. self.uc.from_extended(nums[1:])
  218. case 59:
  219. self.uc.number = 0
  220. }
  221. }
  222. }
  223. type hyperlink_state struct {
  224. id, url string
  225. }
  226. func (self *hyperlink_state) apply_osc(raw string) {
  227. parts := strings.SplitN(raw, ";", 3)
  228. if len(parts) != 3 || parts[0] != "8" {
  229. return
  230. }
  231. self.id = parts[1]
  232. self.url = parts[2]
  233. }
  234. func (self *hyperlink_state) reset() {
  235. self.id = ""
  236. self.url = ""
  237. }
  238. func (self hyperlink_state) as_escape_codes(for_close bool) string {
  239. if self.id == "" && self.url == "" {
  240. return ""
  241. }
  242. if for_close {
  243. return "\x1b]8;;\x1b\\"
  244. }
  245. return fmt.Sprintf("\x1b]8;%s;%s\x1b\\", self.id, self.url)
  246. }
  247. type line_builder struct {
  248. buf []byte
  249. cursor_pos int
  250. seen_non_space_chars bool
  251. pos_of_trailing_whitespace int
  252. }
  253. func (self *line_builder) reset(trim_whitespace bool) string {
  254. ans := string(self.buf)
  255. if trim_whitespace && self.pos_of_trailing_whitespace > -1 {
  256. prefix := ans[:self.pos_of_trailing_whitespace]
  257. tp := strings.TrimRightFunc(prefix, is_space)
  258. if len(tp) != len(prefix) {
  259. ans = tp + ans[self.pos_of_trailing_whitespace:]
  260. }
  261. }
  262. self.buf = self.buf[:0]
  263. self.cursor_pos = 0
  264. self.seen_non_space_chars = false
  265. self.pos_of_trailing_whitespace = -1
  266. return ans
  267. }
  268. func (self *line_builder) has_space_for_width(w, max_width int) bool {
  269. return w+self.cursor_pos <= max_width
  270. }
  271. func (self *line_builder) add_char(ch rune) {
  272. self.seen_non_space_chars = true
  273. self.buf = utf8.AppendRune(self.buf, ch)
  274. self.cursor_pos += wcswidth.Runewidth(ch)
  275. self.pos_of_trailing_whitespace = -1
  276. }
  277. func (self *line_builder) add_space(ch rune, trim_whitespace bool) {
  278. if !trim_whitespace || self.seen_non_space_chars {
  279. self.buf = utf8.AppendRune(self.buf, ch)
  280. self.pos_of_trailing_whitespace = len(self.buf)
  281. self.cursor_pos += wcswidth.Runewidth(ch)
  282. }
  283. }
  284. func (self *line_builder) add_word(word []byte, width int) {
  285. self.seen_non_space_chars = true
  286. self.pos_of_trailing_whitespace = -1
  287. self.buf = append(self.buf, word...)
  288. self.cursor_pos += width
  289. }
  290. func (self *line_builder) add_indent(word string, width int) {
  291. if word != "" {
  292. self.buf = append(self.buf, word...)
  293. self.cursor_pos += width
  294. }
  295. }
  296. func (self *line_builder) add_escape_code(code string) {
  297. self.buf = append(self.buf, code...)
  298. }
  299. func (self *line_builder) add_escape_code2(prefix string, body []byte, suffix string) {
  300. self.buf = append(self.buf, prefix...)
  301. self.buf = append(self.buf, body...)
  302. self.buf = append(self.buf, suffix...)
  303. }
  304. type escape_code_ struct {
  305. prefix, body, suffix string
  306. }
  307. type word_builder struct {
  308. buf []byte
  309. escape_codes []escape_code_
  310. text_start_position int
  311. wcswidth *wcswidth.WCWidthIterator
  312. }
  313. func (self *word_builder) reset(copy_current_word func([]byte)) {
  314. copy_current_word(self.buf)
  315. self.buf = self.buf[:0]
  316. self.escape_codes = self.escape_codes[:0]
  317. self.text_start_position = 0
  318. self.wcswidth.Reset()
  319. }
  320. func (self *word_builder) is_empty() bool {
  321. return len(self.buf) == 0
  322. }
  323. func (self *word_builder) width() int {
  324. return self.wcswidth.CurrentWidth()
  325. }
  326. func (self *word_builder) add_escape_code(prefix string, body []byte, suffix string) {
  327. e := escape_code_{prefix: prefix, body: string(body), suffix: suffix}
  328. self.escape_codes = append(self.escape_codes, e)
  329. self.buf = append(self.buf, prefix...)
  330. self.buf = append(self.buf, body...)
  331. self.buf = append(self.buf, suffix...)
  332. }
  333. func (self *word_builder) has_text() bool { return self.text_start_position != 0 }
  334. func (self *word_builder) recalculate_width() {
  335. self.wcswidth.Reset()
  336. self.wcswidth.Parse(self.buf)
  337. }
  338. func (self *word_builder) add_rune(ch rune) (num_bytes_written int) {
  339. before := len(self.buf)
  340. self.buf = utf8.AppendRune(self.buf, ch)
  341. num_bytes_written = len(self.buf) - before
  342. for _, b := range self.buf[before:] {
  343. self.wcswidth.ParseByte(b)
  344. }
  345. if self.text_start_position == 0 {
  346. self.text_start_position = len(self.buf)
  347. }
  348. return
  349. }
  350. func (self *word_builder) remove_trailing_bytes(n int) {
  351. self.buf = self.buf[:len(self.buf)-n]
  352. self.recalculate_width()
  353. }
  354. type wrapper struct {
  355. ep wcswidth.EscapeCodeParser
  356. indent string
  357. width, indent_width int
  358. trim_whitespace bool
  359. sgr sgr_state
  360. hyperlink hyperlink_state
  361. current_word word_builder
  362. current_line line_builder
  363. lines []string
  364. ignore_lines_containing []string
  365. }
  366. func (self *wrapper) newline_prefix() {
  367. self.current_line.add_escape_code(self.sgr.as_escape_codes(true))
  368. self.current_line.add_escape_code(self.hyperlink.as_escape_codes(true))
  369. self.current_line.add_indent(self.indent, self.indent_width)
  370. self.current_line.add_escape_code(self.sgr.as_escape_codes(false))
  371. self.current_line.add_escape_code(self.hyperlink.as_escape_codes(false))
  372. }
  373. func (self *wrapper) append_line(line string) {
  374. for _, q := range self.ignore_lines_containing {
  375. if strings.Contains(line, q) {
  376. return
  377. }
  378. }
  379. self.lines = append(self.lines, line)
  380. }
  381. func (self *wrapper) end_current_line() {
  382. line := self.current_line.reset(self.trim_whitespace)
  383. if strings.HasSuffix(line, self.indent) && wcswidth.Stringwidth(line) == self.indent_width {
  384. line = line[:len(line)-len(self.indent)]
  385. }
  386. self.append_line(line)
  387. self.newline_prefix()
  388. }
  389. func (self *wrapper) print_word() {
  390. w := self.current_word.width()
  391. if !self.current_line.has_space_for_width(w, self.width) {
  392. self.end_current_line()
  393. w = self.current_word.width()
  394. }
  395. for _, e := range self.current_word.escape_codes {
  396. if e.suffix != "" {
  397. self.hyperlink.apply_osc(e.body)
  398. } else {
  399. self.sgr.apply_csi(e.body)
  400. }
  401. }
  402. self.current_word.reset(func(word []byte) {
  403. self.current_line.add_word(word, w)
  404. })
  405. }
  406. func is_space(ch rune) bool {
  407. return ch != 0xa0 && unicode.IsSpace(ch)
  408. }
  409. func (self *wrapper) handle_rune(ch rune) error {
  410. if ch == '\n' {
  411. self.print_word()
  412. self.end_current_line()
  413. } else if is_space(ch) {
  414. if self.current_word.has_text() {
  415. self.print_word()
  416. }
  417. if self.current_line.cursor_pos >= self.width {
  418. self.end_current_line()
  419. }
  420. self.current_line.add_space(ch, self.trim_whitespace)
  421. } else {
  422. num_of_bytes_written := self.current_word.add_rune(ch)
  423. if self.current_word.width() > self.width {
  424. self.current_word.remove_trailing_bytes(num_of_bytes_written)
  425. self.print_word()
  426. return self.handle_rune(ch)
  427. }
  428. }
  429. return nil
  430. }
  431. func (self *wrapper) handle_csi(raw []byte) error {
  432. self.current_word.add_escape_code("\x1b[", raw, "")
  433. return nil
  434. }
  435. func (self *wrapper) handle_osc(raw []byte) error {
  436. self.current_word.add_escape_code("\x1b]", raw, "\x1b\\")
  437. return nil
  438. }
  439. func (self *wrapper) wrap_text(text string) []string {
  440. if text == "" {
  441. return []string{""}
  442. }
  443. self.current_line.reset(self.trim_whitespace)
  444. self.current_word.reset(func([]byte) {})
  445. self.lines = self.lines[:0]
  446. self.current_line.add_indent(self.indent, self.indent_width)
  447. self.ep.ParseString(text)
  448. if !self.current_word.is_empty() {
  449. self.print_word()
  450. }
  451. self.end_current_line()
  452. last_line := self.current_line.reset(self.trim_whitespace)
  453. self.newline_prefix()
  454. if last_line == self.current_line.reset(self.trim_whitespace) {
  455. last_line = ""
  456. }
  457. if last_line != "" {
  458. self.append_line(last_line)
  459. }
  460. return self.lines
  461. }
  462. func new_wrapper(opts WrapOptions, width int) *wrapper {
  463. width = max(2, width)
  464. ans := wrapper{indent: opts.Indent, width: width, trim_whitespace: opts.Trim_whitespace, indent_width: wcswidth.Stringwidth(opts.Indent)}
  465. if opts.Ignore_lines_containing != "" {
  466. ans.ignore_lines_containing = utils.Splitlines(opts.Ignore_lines_containing)
  467. }
  468. ans.ep.HandleRune = ans.handle_rune
  469. ans.ep.HandleCSI = ans.handle_csi
  470. ans.ep.HandleOSC = ans.handle_osc
  471. ans.lines = make([]string, 0, 32)
  472. ans.current_word.escape_codes = make([]escape_code_, 0, 8)
  473. ans.current_word.wcswidth = wcswidth.CreateWCWidthIterator()
  474. return &ans
  475. }
  476. type WrapOptions struct {
  477. Ignore_lines_containing string
  478. Trim_whitespace bool // trim whitespace at the start and end of lines (start is after indent).
  479. Indent string // indent to add at the start of every line all formatting is cleared for the indent.
  480. }
  481. func WrapTextAsLines(text string, width int, opts WrapOptions) []string {
  482. w := new_wrapper(opts, width)
  483. return w.wrap_text(text)
  484. }
  485. func WrapText(text string, width int, opts WrapOptions) string {
  486. return strings.Join(WrapTextAsLines(text, width, opts), "\n")
  487. }