annotations.ts 3.3 KB

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