import * as React from 'react'; import fastIsEqual from 'react-fast-compare'; import { Location, DocumentUri, Diagnostic, DiagnosticSeverity, PublishDiagnosticsParams } from 'vscode-languageserver-protocol'; import { LeanDiagnostic, RpcErrorCode } from '@leanprover/infoview-api'; import { basename, escapeHtml, usePausableState, useEvent, addUniqueKeys, DocumentPosition, useServerNotificationState, useEventResult } from '../../../../node_modules/lean4-infoview/src/infoview/util'; import { ConfigContext, EditorContext, LspDiagnosticsContext, VersionContext } from '../../../../node_modules/lean4-infoview/src/infoview/contexts'; import { Details } from '../../../../node_modules/lean4-infoview/src/infoview/collapsing'; import { InteractiveMessage } from '../../../../node_modules/lean4-infoview/src/infoview/traceExplorer'; import { getInteractiveDiagnostics, InteractiveDiagnostic, TaggedText_stripTags } from '@leanprover/infoview-api'; import { RpcContext, useRpcSessionAtPos } from '../../../../node_modules/lean4-infoview/src/infoview/rpcSessions'; interface MessageViewProps { uri: DocumentUri; diag: InteractiveDiagnostic; } const MessageView = React.memo(({uri, diag}: MessageViewProps) => { const ec = React.useContext(EditorContext); const fname = escapeHtml(basename(uri)); const {line, character} = diag.range.start; const loc: Location = { uri, range: diag.range }; const text = TaggedText_stripTags(diag.message); const severityClass = diag.severity ? { [DiagnosticSeverity.Error]: 'error', [DiagnosticSeverity.Warning]: 'warning', [DiagnosticSeverity.Information]: 'information', [DiagnosticSeverity.Hint]: 'hint', }[diag.severity] : ''; const title = `Line ${line+1}, Character ${character}`; // Hide "unsolved goals" messages let message; if ("append" in diag.message && "text" in diag.message.append[0] && diag.message?.append[0].text === "unsolved goals") { message = diag.message.append[0] } else { message = diag.message } return ( //
// {title} // // { e.preventDefault(); void ec.revealLocation(loc); }} // title="reveal file location"> // {e.preventDefault(); void ec.copyToComment(text)}} // title="copy message to comment"> // {e.preventDefault(); void ec.api.copyToClipboard(text)}} // title="copy message to clipboard"> // //

{title}

                
            
//
) }, fastIsEqual) function mkMessageViewProps(uri: DocumentUri, messages: InteractiveDiagnostic[]): MessageViewProps[] { const views: MessageViewProps[] = messages .sort((msga, msgb) => { const a = msga.fullRange?.end || msga.range.end; const b = msgb.fullRange?.end || msgb.range.end; return a.line === b.line ? a.character - b.character : a.line - b.line }).map(m => { return { uri, diag: m }; }); return addUniqueKeys(views, v => DocumentPosition.toString({uri: v.uri, ...v.diag.range.start})); } /** Shows the given messages assuming they are for the given file. */ export const MessagesList = React.memo(({uri, messages}: {uri: DocumentUri, messages: InteractiveDiagnostic[]}) => { const should_hide = messages.length === 0; if (should_hide) { return <> } return (
{mkMessageViewProps(uri, messages).map(m => )}
); }) function lazy(f: () => T): () => T { let state: {t: T} | undefined return () => { if (!state) state = {t: f()} return state.t } } /** Displays all messages for the specified file. Can be paused. */ export function AllMessages() { const ec = React.useContext(EditorContext); const sv = React.useContext(VersionContext); const curPos: DocumentPosition | undefined = useEventResult(ec.events.changedCursorLocation, loc => loc ? { uri: loc.uri, ...loc.range.start } : undefined) const rs0 = useRpcSessionAtPos({ uri: curPos.uri, line: 0, character: 0 }); const dc = React.useContext(LspDiagnosticsContext); const config = React.useContext(ConfigContext); const diags0 = dc.get(curPos.uri) || []; const iDiags0 = React.useMemo(() => lazy(async () => { try { const diags = await getInteractiveDiagnostics(rs0); if (diags.length > 0) { return diags } } catch (err: any) { if (err?.code === RpcErrorCode.ContentModified) { // Document has been changed since we made the request. This can happen // while typing quickly. When the server catches up on next edit, it will // send new diagnostics to which the infoview responds by calling // `getInteractiveDiagnostics` again. } else { console.log('getInteractiveDiagnostics error ', err) } } return diags0.map(d => ({ ...(d as LeanDiagnostic), message: { text: d.message } })); }), [sv, rs0, curPos.uri, diags0]); const [{ isPaused, setPaused }, [uri, rs, diags, iDiags], _] = usePausableState(false, [curPos.uri, rs0, diags0, iDiags0]); // Fetch interactive diagnostics when we're entering the paused state // (if they haven't already been fetched before) React.useEffect(() => { if (isPaused) { void iDiags() } }, [iDiags, isPaused]); const setOpenRef = React.useRef>>(); useEvent(ec.events.requestedAction, act => { if (act.kind === 'toggleAllMessages' && setOpenRef.current !== undefined) { setOpenRef.current(t => !t); } }); return ( {/*
All Messages ({diags.length}) { e.preventDefault(); setPaused(p => !p); }} title={isPaused ? 'continue updating' : 'pause updating'}> */} {/*
*/}
) } /** We factor out the body of {@link AllMessages} which lazily fetches its contents only when expanded. */ function AllMessagesBody({uri, messages}: {uri: DocumentUri, messages: () => Promise}) { const [msgs, setMsgs] = React.useState(undefined) React.useEffect(() => { void messages().then(setMsgs) }, [messages]) if (msgs === undefined) return
Loading messages...
else return } /** * Provides a `LspDiagnosticsContext` which stores the latest version of the * diagnostics as sent by the publishDiagnostics notification. */ export function WithLspDiagnosticsContext({children}: React.PropsWithChildren<{}>) { const [allDiags, _0] = useServerNotificationState( 'textDocument/publishDiagnostics', new Map(), async (params: PublishDiagnosticsParams) => diags => new Map(diags).set(params.uri, params.diagnostics), [] ) return {children} } /** Embeds a non-interactive diagnostic into the type `InteractiveDiagnostic`. */ export function lspDiagToInteractive(diag: Diagnostic): InteractiveDiagnostic { return { ...(diag as LeanDiagnostic), message: { text: diag.message } }; }