markdown.go 9.9 KB

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