annotations.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { EditorView, ViewUpdate } from '@codemirror/view'
  2. import { Diagnostic, linter, lintGutter } from '@codemirror/lint'
  3. import {
  4. Compartment,
  5. Extension,
  6. RangeSet,
  7. RangeValue,
  8. StateEffect,
  9. StateField,
  10. Text,
  11. } from '@codemirror/state'
  12. import { Annotation } from '../../../../../types/annotation'
  13. const compileLintSourceConf = new Compartment()
  14. export const annotations = () => [
  15. compileDiagnosticsState,
  16. compileLintSourceConf.of(compileLogLintSource()),
  17. /**
  18. * The built-in lint gutter extension, configured with zero hover delay.
  19. */
  20. lintGutter({
  21. hoverTime: 0,
  22. }),
  23. /**
  24. * A theme which moves the lint gutter outside the line numbers.
  25. */
  26. EditorView.baseTheme({
  27. '.cm-gutter-lint': {
  28. order: -1,
  29. },
  30. }),
  31. ]
  32. export const lintSourceConfig = {
  33. delay: 100,
  34. // Show highlights only for errors
  35. markerFilter(diagnostics: readonly Diagnostic[]) {
  36. return diagnostics.filter(d => d.severity === 'error')
  37. },
  38. // Do not show any tooltips for highlights within the editor content
  39. tooltipFilter() {
  40. return []
  41. },
  42. needsRefresh(update: ViewUpdate) {
  43. return update.selectionSet
  44. },
  45. }
  46. /**
  47. * A lint source using the compile log diagnostics
  48. */
  49. const compileLogLintSource = (): Extension =>
  50. linter(view => {
  51. const items: Diagnostic[] = []
  52. const cursor = view.state.field(compileDiagnosticsState).iter()
  53. while (cursor.value !== null) {
  54. items.push({
  55. ...cursor.value.diagnostic,
  56. from: cursor.from,
  57. to: cursor.to,
  58. })
  59. cursor.next()
  60. }
  61. return items
  62. }, lintSourceConfig)
  63. class DiagnosticRangeValue extends RangeValue {
  64. constructor(public diagnostic: Diagnostic) {
  65. super()
  66. }
  67. }
  68. const setCompileDiagnosticsEffect = StateEffect.define<Diagnostic[]>()
  69. /**
  70. * A state field for the compile log diagnostics
  71. */
  72. export const compileDiagnosticsState = StateField.define<
  73. RangeSet<DiagnosticRangeValue>
  74. >({
  75. create() {
  76. return RangeSet.empty
  77. },
  78. update(value, transaction) {
  79. for (const effect of transaction.effects) {
  80. if (effect.is(setCompileDiagnosticsEffect)) {
  81. return RangeSet.of(
  82. effect.value.map(diagnostic =>
  83. new DiagnosticRangeValue(diagnostic).range(
  84. diagnostic.from,
  85. diagnostic.to
  86. )
  87. ),
  88. true
  89. )
  90. }
  91. }
  92. if (transaction.docChanged) {
  93. value = value.map(transaction.changes)
  94. }
  95. return value
  96. },
  97. })
  98. export const setAnnotations = (doc: Text, annotations: Annotation[]) => {
  99. const diagnostics: Diagnostic[] = []
  100. for (const annotation of annotations) {
  101. // ignore "whole document" (row: -1) annotations
  102. if (annotation.row !== -1) {
  103. try {
  104. diagnostics.push(convertAnnotationToDiagnostic(doc, annotation))
  105. } catch (error) {
  106. // ignore invalid annotations
  107. console.debug('invalid annotation position', error)
  108. }
  109. }
  110. }
  111. return {
  112. effects: setCompileDiagnosticsEffect.of(diagnostics),
  113. }
  114. }
  115. export const showCompileLogDiagnostics = (show: boolean) => {
  116. return {
  117. effects: [
  118. // reconfigure the compile log lint source
  119. compileLintSourceConf.reconfigure(show ? compileLogLintSource() : []),
  120. ],
  121. }
  122. }
  123. const convertAnnotationToDiagnostic = (
  124. doc: Text,
  125. annotation: Annotation
  126. ): Diagnostic => {
  127. if (annotation.row < 0) {
  128. throw new Error(`Invalid annotation row ${annotation.row}`)
  129. }
  130. const line = doc.line(annotation.row + 1)
  131. return {
  132. from: line.from,
  133. to: line.to, // NOTE: highlight whole line as synctex doesn't output column number
  134. severity: annotation.type,
  135. message: annotation.text,
  136. // source: annotation.source, // NOTE: the source is displayed in the tooltip
  137. }
  138. }