Show diagnostics in Code Editor (#8375)

As suggested by @JaroslavTulach.

- Shows diagnostics sent by `executionContext/executionStatus`
- Shows `Panic`s and `DataflowError`s sent by `executionContext/expressionUpdates`

# Important Notes
None
This commit is contained in:
somebody1234 2023-11-24 00:15:48 +10:00 committed by GitHub
parent a9099ddce5
commit 87dfb57f53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 3 deletions

View File

@ -3,8 +3,9 @@ import { useGraphStore } from '@/stores/graph'
import { useProjectStore } from '@/stores/project'
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { useAutoBlur } from '@/util/autoBlur'
import type { Highlighter } from '@/util/codemirror'
import type { Diagnostic, Highlighter } from '@/util/codemirror'
import { usePointer } from '@/util/events'
import { chain } from '@/util/iterable'
import { useLocalStorage } from '@vueuse/core'
import { rangeEncloses, type ExprId } from 'shared/yjsModel'
import { computed, onMounted, ref, watchEffect } from 'vue'
@ -15,6 +16,7 @@ import { unwrap } from '../util/result'
const {
bracketMatching,
foldGutter,
lintGutter,
highlightSelectionMatches,
minimalSetup,
EditorState,
@ -24,6 +26,8 @@ const {
defaultHighlightStyle,
tooltips,
enso,
linter,
lsDiagnosticsToCMDiagnostics,
hoverTooltip,
} = await import('@/util/codemirror')
@ -33,6 +37,41 @@ const suggestionDbStore = useSuggestionDbStore()
const rootElement = ref<HTMLElement>()
useAutoBlur(rootElement)
const executionContextDiagnostics = computed(() =>
projectStore.module
? lsDiagnosticsToCMDiagnostics(
projectStore.module.doc.contents.toString(),
projectStore.diagnostics,
)
: [],
)
const expressionUpdatesDiagnostics = computed(() => {
const nodeMap = graphStore.db.nodeIdToNode
const updates = projectStore.computedValueRegistry.db
const panics = updates.type.reverseLookup('Panic')
const errors = updates.type.reverseLookup('DataflowError')
const diagnostics: Diagnostic[] = []
for (const id of chain(panics, errors)) {
const update = updates.get(id)
if (!update) continue
const node = nodeMap.get(id)
if (!node) continue
const [from, to] = node.rootSpan.span()
switch (update.payload.type) {
case 'Panic': {
diagnostics.push({ from, to, message: update.payload.message, severity: 'error' })
break
}
case 'DataflowError': {
diagnostics.push({ from, to, message: 'Unknown data flow error', severity: 'error' })
break
}
}
}
return diagnostics
})
// == CodeMirror editor setup ==
const editorView = new EditorView()
@ -51,6 +90,7 @@ watchEffect(() => {
syntaxHighlighting(defaultHighlightStyle as Highlighter),
bracketMatching(),
foldGutter(),
lintGutter(),
highlightSelectionMatches(),
tooltips({ position: 'absolute' }),
hoverTooltip((ast, syn) => {
@ -100,6 +140,7 @@ watchEffect(() => {
return { dom }
}),
enso(),
linter(() => [...executionContextDiagnostics.value, ...expressionUpdatesDiagnostics.value]),
],
}),
)

View File

@ -525,6 +525,11 @@ export const useProjectStore = defineStore('project', () => {
const computedValueRegistry = ComputedValueRegistry.WithExecutionContext(executionContext)
const visualizationDataRegistry = new VisualizationDataRegistry(executionContext, dataConnection)
const diagnostics = ref<Diagnostic[]>([])
executionContext.on('executionStatus', (newDiagnostics) => {
diagnostics.value = newDiagnostics
})
function useVisualizationData(
configuration: WatchSource<Opt<NodeVisualizationConfiguration>>,
): ShallowRef<{} | undefined> {
@ -561,6 +566,7 @@ export const useProjectStore = defineStore('project', () => {
},
name: projectName,
executionContext,
diagnostics,
module,
modulePath,
projectModel,

View File

@ -11,6 +11,7 @@ export {
foldNodeProp,
syntaxHighlighting,
} from '@codemirror/language'
export { lintGutter, linter, type Diagnostic } from '@codemirror/lint'
export { highlightSelectionMatches } from '@codemirror/search'
export { EditorState } from '@codemirror/state'
export { EditorView, tooltips, type TooltipView } from '@codemirror/view'
@ -26,6 +27,7 @@ import {
languageDataProp,
syntaxTree,
} from '@codemirror/language'
import { type Diagnostic } from '@codemirror/lint'
import { hoverTooltip as originalHoverTooltip, type TooltipView } from '@codemirror/view'
import {
NodeProp,
@ -39,6 +41,34 @@ import {
} from '@lezer/common'
import { styleTags, tags } from '@lezer/highlight'
import { EditorView } from 'codemirror'
import type { Diagnostic as LSDiagnostic } from 'shared/languageServerTypes'
export function lsDiagnosticsToCMDiagnostics(
source: string,
diagnostics: LSDiagnostic[],
): Diagnostic[] {
if (!diagnostics.length) return []
const results: Diagnostic[] = []
let pos = 0
const lineStartIndices = []
for (const line of source.split('\n')) {
lineStartIndices.push(pos)
pos += line.length + 1
}
for (const diagnostic of diagnostics) {
if (!diagnostic.location) continue
results.push({
from:
(lineStartIndices[diagnostic.location.start.line] ?? 0) +
diagnostic.location.start.character,
to: (lineStartIndices[diagnostic.location.end.line] ?? 0) + diagnostic.location.end.character,
message: diagnostic.message,
severity:
diagnostic.kind === 'Error' ? 'error' : diagnostic.kind === 'Warning' ? 'warning' : 'info',
})
}
return results
}
type AstNode = AstExtended<Ast.Tree | Ast.Token, false>

View File

@ -7,7 +7,7 @@ import type {
ProfilingInfo,
} from 'shared/languageServerTypes'
import { markRaw } from 'vue'
import { ReactiveDb } from './database/reactiveDb'
import { ReactiveDb, ReactiveIndex } from './database/reactiveDb'
export interface ExpressionInfo {
typename: string | undefined
@ -16,9 +16,13 @@ export interface ExpressionInfo {
profilingInfo: ProfilingInfo[]
}
class ComputedValueDb extends ReactiveDb<ExpressionId, ExpressionInfo> {
type = new ReactiveIndex(this, (id, info) => [[id, info.payload.type]])
}
/** This class holds the computed values that have been received from the language server. */
export class ComputedValueRegistry {
public db: ReactiveDb<ExpressionId, ExpressionInfo> = new ReactiveDb()
public db = new ComputedValueDb()
private _updateHandler = this.processUpdates.bind(this)
private executionContext: ExecutionContext | undefined