inline-background.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { Annotation, Compartment } from '@codemirror/state'
  2. import { EditorView, ViewPlugin } from '@codemirror/view'
  3. import { themeOptionsChange } from './theme'
  4. import { sourceOnly } from './visual/visual'
  5. import { round } from 'lodash'
  6. import { hasLanguageLoadedEffect } from './language'
  7. import { fontLoad, hasFontLoadedEffect } from './font-load'
  8. const themeConf = new Compartment()
  9. const changeHalfLeadingAnnotation = Annotation.define<boolean>()
  10. function firstVisibleNonSpacePos(view: EditorView) {
  11. for (const range of view.visibleRanges) {
  12. const match = /\S/.exec(view.state.sliceDoc(range.from, range.to))
  13. if (match) {
  14. return range.from + match.index
  15. }
  16. }
  17. return null
  18. }
  19. function measureHalfLeading(view: EditorView) {
  20. const pos = firstVisibleNonSpacePos(view)
  21. if (pos === null) {
  22. return 0
  23. }
  24. const coords = view.coordsAtPos(pos)
  25. if (!coords) {
  26. return 0
  27. }
  28. const inlineBoxHeight = coords.bottom - coords.top
  29. // Rounding prevents gaps appearing in some situations
  30. return round((view.defaultLineHeight - inlineBoxHeight) / 2, 2)
  31. }
  32. function createTheme(halfLeading: number) {
  33. return EditorView.theme({
  34. '.cm-content': {
  35. '--half-leading': halfLeading + 'px',
  36. },
  37. })
  38. }
  39. const plugin = ViewPlugin.define(
  40. view => {
  41. let halfLeading = 0
  42. const measureRequest = {
  43. read: () => {
  44. return measureHalfLeading(view)
  45. },
  46. write: (newHalfLeading: number) => {
  47. if (newHalfLeading !== halfLeading) {
  48. halfLeading = newHalfLeading
  49. window.setTimeout(() =>
  50. view.dispatch({
  51. effects: themeConf.reconfigure(createTheme(newHalfLeading)),
  52. annotations: changeHalfLeadingAnnotation.of(true),
  53. })
  54. )
  55. }
  56. },
  57. }
  58. return {
  59. update(update) {
  60. // Ignore any update triggered by this plugin
  61. if (
  62. update.transactions.some(tr =>
  63. tr.annotation(changeHalfLeadingAnnotation)
  64. )
  65. ) {
  66. return
  67. }
  68. if (
  69. hasFontLoadedEffect(update) ||
  70. (update.geometryChanged && !update.docChanged) ||
  71. update.transactions.some(tr => tr.annotation(themeOptionsChange)) ||
  72. hasLanguageLoadedEffect(update)
  73. ) {
  74. view.requestMeasure(measureRequest)
  75. }
  76. },
  77. }
  78. },
  79. {
  80. provide: () => [themeConf.of(createTheme(0))],
  81. }
  82. )
  83. export const inlineBackground = (visual: boolean) => {
  84. return sourceOnly(visual, [fontLoad, plugin])
  85. }