annotations.ts 3.7 KB

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