markdown.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package styles
  2. import (
  3. "github.com/charmbracelet/glamour"
  4. "github.com/charmbracelet/glamour/ansi"
  5. "github.com/charmbracelet/lipgloss"
  6. "github.com/sst/opencode/internal/tui/theme"
  7. )
  8. const defaultMargin = 1
  9. // Helper functions for style pointers
  10. func boolPtr(b bool) *bool { return &b }
  11. func stringPtr(s string) *string { return &s }
  12. func uintPtr(u uint) *uint { return &u }
  13. // returns a glamour TermRenderer configured with the current theme
  14. func GetMarkdownRenderer(width int) *glamour.TermRenderer {
  15. r, _ := glamour.NewTermRenderer(
  16. glamour.WithStyles(generateMarkdownStyleConfig()),
  17. glamour.WithWordWrap(width),
  18. )
  19. return r
  20. }
  21. // creates an ansi.StyleConfig for markdown rendering
  22. // using adaptive colors from the provided theme.
  23. func generateMarkdownStyleConfig() ansi.StyleConfig {
  24. t := theme.CurrentTheme()
  25. return ansi.StyleConfig{
  26. Document: ansi.StyleBlock{
  27. StylePrimitive: ansi.StylePrimitive{
  28. BlockPrefix: "",
  29. BlockSuffix: "",
  30. Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
  31. },
  32. Margin: uintPtr(defaultMargin),
  33. },
  34. BlockQuote: ansi.StyleBlock{
  35. StylePrimitive: ansi.StylePrimitive{
  36. Color: stringPtr(adaptiveColorToString(t.MarkdownBlockQuote())),
  37. Italic: boolPtr(true),
  38. Prefix: "┃ ",
  39. },
  40. Indent: uintPtr(1),
  41. IndentToken: stringPtr(BaseStyle().Render(" ")),
  42. },
  43. List: ansi.StyleList{
  44. LevelIndent: defaultMargin,
  45. StyleBlock: ansi.StyleBlock{
  46. IndentToken: stringPtr(BaseStyle().Render(" ")),
  47. StylePrimitive: ansi.StylePrimitive{
  48. Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
  49. },
  50. },
  51. },
  52. Heading: ansi.StyleBlock{
  53. StylePrimitive: ansi.StylePrimitive{
  54. BlockSuffix: "\n",
  55. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  56. Bold: boolPtr(true),
  57. },
  58. },
  59. H1: ansi.StyleBlock{
  60. StylePrimitive: ansi.StylePrimitive{
  61. Prefix: "# ",
  62. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  63. Bold: boolPtr(true),
  64. },
  65. },
  66. H2: ansi.StyleBlock{
  67. StylePrimitive: ansi.StylePrimitive{
  68. Prefix: "## ",
  69. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  70. Bold: boolPtr(true),
  71. },
  72. },
  73. H3: ansi.StyleBlock{
  74. StylePrimitive: ansi.StylePrimitive{
  75. Prefix: "### ",
  76. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  77. Bold: boolPtr(true),
  78. },
  79. },
  80. H4: ansi.StyleBlock{
  81. StylePrimitive: ansi.StylePrimitive{
  82. Prefix: "#### ",
  83. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  84. Bold: boolPtr(true),
  85. },
  86. },
  87. H5: ansi.StyleBlock{
  88. StylePrimitive: ansi.StylePrimitive{
  89. Prefix: "##### ",
  90. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  91. Bold: boolPtr(true),
  92. },
  93. },
  94. H6: ansi.StyleBlock{
  95. StylePrimitive: ansi.StylePrimitive{
  96. Prefix: "###### ",
  97. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  98. Bold: boolPtr(true),
  99. },
  100. },
  101. Strikethrough: ansi.StylePrimitive{
  102. CrossedOut: boolPtr(true),
  103. Color: stringPtr(adaptiveColorToString(t.TextMuted())),
  104. },
  105. Emph: ansi.StylePrimitive{
  106. Color: stringPtr(adaptiveColorToString(t.MarkdownEmph())),
  107. Italic: boolPtr(true),
  108. },
  109. Strong: ansi.StylePrimitive{
  110. Bold: boolPtr(true),
  111. Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
  112. },
  113. HorizontalRule: ansi.StylePrimitive{
  114. Color: stringPtr(adaptiveColorToString(t.MarkdownHorizontalRule())),
  115. Format: "\n─────────────────────────────────────────\n",
  116. },
  117. Item: ansi.StylePrimitive{
  118. BlockPrefix: "• ",
  119. Color: stringPtr(adaptiveColorToString(t.MarkdownListItem())),
  120. },
  121. Enumeration: ansi.StylePrimitive{
  122. BlockPrefix: ". ",
  123. Color: stringPtr(adaptiveColorToString(t.MarkdownListEnumeration())),
  124. },
  125. Task: ansi.StyleTask{
  126. StylePrimitive: ansi.StylePrimitive{},
  127. Ticked: "[✓] ",
  128. Unticked: "[ ] ",
  129. },
  130. Link: ansi.StylePrimitive{
  131. Color: stringPtr(adaptiveColorToString(t.MarkdownLink())),
  132. Underline: boolPtr(true),
  133. },
  134. LinkText: ansi.StylePrimitive{
  135. Color: stringPtr(adaptiveColorToString(t.MarkdownLinkText())),
  136. Bold: boolPtr(true),
  137. },
  138. Image: ansi.StylePrimitive{
  139. Color: stringPtr(adaptiveColorToString(t.MarkdownImage())),
  140. Underline: boolPtr(true),
  141. Format: "🖼 {{.text}}",
  142. },
  143. ImageText: ansi.StylePrimitive{
  144. Color: stringPtr(adaptiveColorToString(t.MarkdownImageText())),
  145. Format: "{{.text}}",
  146. },
  147. Code: ansi.StyleBlock{
  148. StylePrimitive: ansi.StylePrimitive{
  149. Color: stringPtr(adaptiveColorToString(t.MarkdownCode())),
  150. Prefix: "",
  151. Suffix: "",
  152. },
  153. },
  154. CodeBlock: ansi.StyleCodeBlock{
  155. StyleBlock: ansi.StyleBlock{
  156. StylePrimitive: ansi.StylePrimitive{
  157. Prefix: " ",
  158. Color: stringPtr(adaptiveColorToString(t.MarkdownCodeBlock())),
  159. },
  160. },
  161. Chroma: &ansi.Chroma{
  162. Text: ansi.StylePrimitive{
  163. Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
  164. },
  165. Error: ansi.StylePrimitive{
  166. Color: stringPtr(adaptiveColorToString(t.Error())),
  167. },
  168. Comment: ansi.StylePrimitive{
  169. Color: stringPtr(adaptiveColorToString(t.SyntaxComment())),
  170. },
  171. CommentPreproc: ansi.StylePrimitive{
  172. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  173. },
  174. Keyword: ansi.StylePrimitive{
  175. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  176. },
  177. KeywordReserved: ansi.StylePrimitive{
  178. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  179. },
  180. KeywordNamespace: ansi.StylePrimitive{
  181. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  182. },
  183. KeywordType: ansi.StylePrimitive{
  184. Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
  185. },
  186. Operator: ansi.StylePrimitive{
  187. Color: stringPtr(adaptiveColorToString(t.SyntaxOperator())),
  188. },
  189. Punctuation: ansi.StylePrimitive{
  190. Color: stringPtr(adaptiveColorToString(t.SyntaxPunctuation())),
  191. },
  192. Name: ansi.StylePrimitive{
  193. Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
  194. },
  195. NameBuiltin: ansi.StylePrimitive{
  196. Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
  197. },
  198. NameTag: ansi.StylePrimitive{
  199. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  200. },
  201. NameAttribute: ansi.StylePrimitive{
  202. Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
  203. },
  204. NameClass: ansi.StylePrimitive{
  205. Color: stringPtr(adaptiveColorToString(t.SyntaxType())),
  206. },
  207. NameConstant: ansi.StylePrimitive{
  208. Color: stringPtr(adaptiveColorToString(t.SyntaxVariable())),
  209. },
  210. NameDecorator: ansi.StylePrimitive{
  211. Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
  212. },
  213. NameFunction: ansi.StylePrimitive{
  214. Color: stringPtr(adaptiveColorToString(t.SyntaxFunction())),
  215. },
  216. LiteralNumber: ansi.StylePrimitive{
  217. Color: stringPtr(adaptiveColorToString(t.SyntaxNumber())),
  218. },
  219. LiteralString: ansi.StylePrimitive{
  220. Color: stringPtr(adaptiveColorToString(t.SyntaxString())),
  221. },
  222. LiteralStringEscape: ansi.StylePrimitive{
  223. Color: stringPtr(adaptiveColorToString(t.SyntaxKeyword())),
  224. },
  225. GenericDeleted: ansi.StylePrimitive{
  226. Color: stringPtr(adaptiveColorToString(t.DiffRemoved())),
  227. },
  228. GenericEmph: ansi.StylePrimitive{
  229. Color: stringPtr(adaptiveColorToString(t.MarkdownEmph())),
  230. Italic: boolPtr(true),
  231. },
  232. GenericInserted: ansi.StylePrimitive{
  233. Color: stringPtr(adaptiveColorToString(t.DiffAdded())),
  234. },
  235. GenericStrong: ansi.StylePrimitive{
  236. Color: stringPtr(adaptiveColorToString(t.MarkdownStrong())),
  237. Bold: boolPtr(true),
  238. },
  239. GenericSubheading: ansi.StylePrimitive{
  240. Color: stringPtr(adaptiveColorToString(t.MarkdownHeading())),
  241. },
  242. },
  243. },
  244. Table: ansi.StyleTable{
  245. StyleBlock: ansi.StyleBlock{
  246. StylePrimitive: ansi.StylePrimitive{
  247. BlockPrefix: "\n",
  248. BlockSuffix: "\n",
  249. },
  250. },
  251. CenterSeparator: stringPtr("┼"),
  252. ColumnSeparator: stringPtr("│"),
  253. RowSeparator: stringPtr("─"),
  254. },
  255. DefinitionDescription: ansi.StylePrimitive{
  256. BlockPrefix: "\n ❯ ",
  257. Color: stringPtr(adaptiveColorToString(t.MarkdownLinkText())),
  258. },
  259. Text: ansi.StylePrimitive{
  260. Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
  261. },
  262. Paragraph: ansi.StyleBlock{
  263. StylePrimitive: ansi.StylePrimitive{
  264. Color: stringPtr(adaptiveColorToString(t.MarkdownText())),
  265. },
  266. },
  267. }
  268. }
  269. // adaptiveColorToString converts a lipgloss.AdaptiveColor to the appropriate
  270. // hex color string based on the current terminal background
  271. func adaptiveColorToString(color lipgloss.AdaptiveColor) string {
  272. if lipgloss.HasDarkBackground() {
  273. return color.Dark
  274. }
  275. return color.Light
  276. }