ソースを参照

feat: making highlighting of errors more specific (#19963)

GitOrigin-RevId: 63bc147e18e80c1e070722bc70114f8fca8509ae
Alf Eaton 1 年間 前
コミット
607b3e3494

+ 1 - 0
services/web/frontend/js/features/pdf-preview/util/output-files.js

@@ -183,6 +183,7 @@ export function buildLogEntryAnnotations(entries, fileTreeData, rootDocId) {
           text: entry.message,
           text: entry.message,
           source: 'compile', // NOTE: this is used in Ace for filtering the annotations
           source: 'compile', // NOTE: this is used in Ace for filtering the annotations
           ruleId: entry.ruleId,
           ruleId: entry.ruleId,
+          command: entry.command,
         }
         }
 
 
         // set firstOnLine for the first non-typesetting annotation on a line
         // set firstOnLine for the first non-typesetting annotation on a line

+ 56 - 10
services/web/frontend/js/features/source-editor/extensions/annotations.ts

@@ -2,17 +2,19 @@ import { EditorView, ViewUpdate } from '@codemirror/view'
 import { Diagnostic, linter, lintGutter } from '@codemirror/lint'
 import { Diagnostic, linter, lintGutter } from '@codemirror/lint'
 import {
 import {
   Compartment,
   Compartment,
+  EditorState,
   Extension,
   Extension,
+  Line,
   RangeSet,
   RangeSet,
   RangeValue,
   RangeValue,
   StateEffect,
   StateEffect,
   StateField,
   StateField,
-  Text,
 } from '@codemirror/state'
 } from '@codemirror/state'
 import { Annotation } from '../../../../../types/annotation'
 import { Annotation } from '../../../../../types/annotation'
 import { debugConsole } from '@/utils/debugging'
 import { debugConsole } from '@/utils/debugging'
 import { sendMB } from '@/infrastructure/event-tracking'
 import { sendMB } from '@/infrastructure/event-tracking'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
+import { syntaxTree } from '@codemirror/language'
 
 
 interface CompileLogDiagnostic extends Diagnostic {
 interface CompileLogDiagnostic extends Diagnostic {
   compile?: true
   compile?: true
@@ -142,14 +144,17 @@ export const compileDiagnosticsState = StateField.define<
   },
   },
 })
 })
 
 
-export const setAnnotations = (doc: Text, annotations: Annotation[]) => {
+export const setAnnotations = (
+  state: EditorState,
+  annotations: Annotation[]
+) => {
   const diagnostics: CompileLogDiagnostic[] = []
   const diagnostics: CompileLogDiagnostic[] = []
 
 
   for (const annotation of annotations) {
   for (const annotation of annotations) {
     // ignore "whole document" (row: -1) annotations
     // ignore "whole document" (row: -1) annotations
     if (annotation.row !== -1) {
     if (annotation.row !== -1) {
       try {
       try {
-        diagnostics.push(convertAnnotationToDiagnostic(doc, annotation))
+        diagnostics.push(...convertAnnotationToDiagnostic(state, annotation))
       } catch (error) {
       } catch (error) {
         // ignore invalid annotations
         // ignore invalid annotations
         debugConsole.debug('invalid annotation position', error)
         debugConsole.debug('invalid annotation position', error)
@@ -171,19 +176,60 @@ export const showCompileLogDiagnostics = (show: boolean) => {
   }
   }
 }
 }
 
 
+const commandRanges = (state: EditorState, line: Line, command: string) => {
+  const ranges: { from: number; to: number }[] = []
+
+  syntaxTree(state).iterate({
+    enter(nodeRef) {
+      if (nodeRef.type.is('CtrlSeq')) {
+        const { from, to } = nodeRef
+        if (command === state.sliceDoc(from, to)) {
+          ranges.push({ from, to })
+        }
+      }
+    },
+    from: line.from,
+    to: line.to,
+  })
+
+  return ranges.slice(0, 1) // NOTE: only highlighting the first match on a line, to avoid duplicate messages
+}
+
+const chooseHighlightRanges = (
+  state: EditorState,
+  line: Line,
+  annotation: Annotation
+) => {
+  const ranges: { from: number; to: number }[] = []
+
+  if (annotation.command) {
+    ranges.push(...commandRanges(state, line, annotation.command))
+  }
+
+  // default to highlighting the whole line
+  if (ranges.length === 0) {
+    ranges.push(line)
+  }
+
+  return ranges
+}
+
 const convertAnnotationToDiagnostic = (
 const convertAnnotationToDiagnostic = (
-  doc: Text,
+  state: EditorState,
   annotation: Annotation
   annotation: Annotation
-): CompileLogDiagnostic => {
+): CompileLogDiagnostic[] => {
   if (annotation.row < 0) {
   if (annotation.row < 0) {
     throw new Error(`Invalid annotation row ${annotation.row}`)
     throw new Error(`Invalid annotation row ${annotation.row}`)
   }
   }
 
 
-  const line = doc.line(annotation.row + 1)
+  // NOTE: highlight whole line by default, as synctex doesn't output column number
+  const line = state.doc.line(annotation.row + 1)
 
 
-  return {
-    from: line.from,
-    to: line.to, // NOTE: highlight whole line as synctex doesn't output column number
+  const highlightRanges = chooseHighlightRanges(state, line, annotation)
+
+  return highlightRanges.map(location => ({
+    from: location.from,
+    to: location.to,
     severity: annotation.type,
     severity: annotation.type,
     message: annotation.text,
     message: annotation.text,
     ruleId: annotation.ruleId,
     ruleId: annotation.ruleId,
@@ -192,7 +238,7 @@ const convertAnnotationToDiagnostic = (
     entryIndex: annotation.entryIndex,
     entryIndex: annotation.entryIndex,
     source: annotation.source,
     source: annotation.source,
     firstOnLine: annotation.firstOnLine,
     firstOnLine: annotation.firstOnLine,
-  }
+  }))
 }
 }
 
 
 export const renderMessage = (diagnostic: RenderedDiagnostic) => {
 export const renderMessage = (diagnostic: RenderedDiagnostic) => {

+ 1 - 1
services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts

@@ -497,7 +497,7 @@ function useCodeMirrorScope(view: EditorView) {
       // dispatch in a timeout, so the dispatch isn't in the same cycle as the edit which caused it
       // dispatch in a timeout, so the dispatch isn't in the same cycle as the edit which caused it
       window.setTimeout(() => {
       window.setTimeout(() => {
         view.dispatch(
         view.dispatch(
-          setAnnotations(view.state.doc, annotations || []),
+          setAnnotations(view.state, annotations || []),
           // reconfigure the compile log lint source, so it runs once with the new data
           // reconfigure the compile log lint source, so it runs once with the new data
           showCompileLogDiagnostics(enableCompileLogLinterRef.current)
           showCompileLogDiagnostics(enableCompileLogLinterRef.current)
         )
         )

+ 4 - 0
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogs.js

@@ -51,6 +51,10 @@ export default {
           }
           }
         }
         }
 
 
+        if (entry.contentDetails && ruleDetails.highlightCommand) {
+          entry.command = ruleDetails.highlightCommand(entry.contentDetails)
+        }
+
         // suppress any entries that are known to cascade from previous error types
         // suppress any entries that are known to cascade from previous error types
         if (ruleDetails.cascadesFrom) {
         if (ruleDetails.cascadesFrom) {
           for (const type of ruleDetails.cascadesFrom) {
           for (const type of ruleDetails.cascadesFrom) {

+ 4 - 0
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsRules.tsx

@@ -16,6 +16,7 @@ interface Rule {
     details?: [string]
     details?: [string]
   ) => string | [string, JSX.Element]
   ) => string | [string, JSX.Element]
   package?: string
   package?: string
+  highlightCommand?: (contentDetails: string[]) => string | undefined
 }
 }
 
 
 const rules: Rule[] = [
 const rules: Rule[] = [
@@ -78,6 +79,9 @@ const rules: Rule[] = [
       }
       }
       return currentTitle
       return currentTitle
     },
     },
+    highlightCommand(contentDetails) {
+      return contentDetails[0]
+    },
   },
   },
   {
   {
     ruleId: 'hint_undefined_environment',
     ruleId: 'hint_undefined_environment',

+ 1 - 0
services/web/types/annotation.ts

@@ -7,4 +7,5 @@ export type Annotation = {
   id: string
   id: string
   entryIndex: number
   entryIndex: number
   firstOnLine: boolean
   firstOnLine: boolean
+  command?: string
 }
 }