lean4monaco 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/monaco-lean4/lean4-infoview/src/index.d.ts +16 -0
  2. package/dist/monaco-lean4/lean4-infoview/src/index.js +29 -0
  3. package/dist/monaco-lean4/lean4-infoview/src/infoview/collapsing.d.ts +12 -0
  4. package/dist/monaco-lean4/lean4-infoview/src/infoview/collapsing.js +37 -0
  5. package/dist/monaco-lean4/lean4-infoview/src/infoview/contexts.d.ts +40 -0
  6. package/dist/monaco-lean4/lean4-infoview/src/infoview/contexts.js +44 -0
  7. package/dist/monaco-lean4/lean4-infoview/src/infoview/editorConnection.d.ts +22 -0
  8. package/dist/monaco-lean4/lean4-infoview/src/infoview/editorConnection.js +41 -0
  9. package/dist/monaco-lean4/lean4-infoview/src/infoview/errors.d.ts +14 -0
  10. package/dist/monaco-lean4/lean4-infoview/src/infoview/errors.js +24 -0
  11. package/dist/monaco-lean4/lean4-infoview/src/infoview/event.d.ts +33 -0
  12. package/dist/monaco-lean4/lean4-infoview/src/infoview/event.js +57 -0
  13. package/dist/monaco-lean4/lean4-infoview/src/infoview/goalLocation.d.ts +61 -0
  14. package/dist/monaco-lean4/lean4-infoview/src/infoview/goalLocation.js +87 -0
  15. package/dist/monaco-lean4/lean4-infoview/src/infoview/goals.d.ts +11 -0
  16. package/dist/monaco-lean4/lean4-infoview/src/infoview/goals.js +141 -0
  17. package/dist/monaco-lean4/lean4-infoview/src/infoview/info.d.ts +18 -0
  18. package/dist/monaco-lean4/lean4-infoview/src/infoview/info.js +278 -0
  19. package/dist/monaco-lean4/lean4-infoview/src/infoview/infos.d.ts +2 -0
  20. package/dist/monaco-lean4/lean4-infoview/src/infoview/infos.js +113 -0
  21. package/dist/monaco-lean4/lean4-infoview/src/infoview/interactiveCode.d.ts +18 -0
  22. package/dist/monaco-lean4/lean4-infoview/src/infoview/interactiveCode.js +164 -0
  23. package/dist/monaco-lean4/lean4-infoview/src/infoview/main.d.ts +13 -0
  24. package/dist/monaco-lean4/lean4-infoview/src/infoview/main.js +97 -0
  25. package/dist/monaco-lean4/lean4-infoview/src/infoview/messages.d.ts +16 -0
  26. package/dist/monaco-lean4/lean4-infoview/src/infoview/messages.js +151 -0
  27. package/dist/monaco-lean4/lean4-infoview/src/infoview/rpcSessions.d.ts +21 -0
  28. package/dist/monaco-lean4/lean4-infoview/src/infoview/rpcSessions.js +67 -0
  29. package/dist/monaco-lean4/lean4-infoview/src/infoview/serverVersion.d.ts +10 -0
  30. package/dist/monaco-lean4/lean4-infoview/src/infoview/serverVersion.js +25 -0
  31. package/dist/monaco-lean4/lean4-infoview/src/infoview/tooltips.d.ts +23 -0
  32. package/dist/monaco-lean4/lean4-infoview/src/infoview/tooltips.js +231 -0
  33. package/dist/monaco-lean4/lean4-infoview/src/infoview/traceExplorer.d.ts +11 -0
  34. package/dist/monaco-lean4/lean4-infoview/src/infoview/traceExplorer.js +115 -0
  35. package/dist/monaco-lean4/lean4-infoview/src/infoview/userWidget.d.ts +48 -0
  36. package/dist/monaco-lean4/lean4-infoview/src/infoview/userWidget.js +54 -0
  37. package/dist/monaco-lean4/lean4-infoview/src/infoview/util.d.ts +144 -0
  38. package/dist/monaco-lean4/lean4-infoview/src/infoview/util.js +366 -0
  39. package/dist/monaco-lean4/vscode-lean4/src/utils/batch.d.ts +0 -1
  40. package/dist/monaco-lean4/vscode-lean4/src/utils/envPath.d.ts +0 -1
  41. package/dist/monaco-lean4/vscode-lean4/src/utils/fsHelper.d.ts +0 -1
  42. package/package.json +3 -4
@@ -0,0 +1,164 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { getGoToLocation, InteractiveDiagnostics_infoToInteractive, TaggedText_stripTags, } from '@leanprover/infoview-api';
4
+ import { marked } from 'marked';
5
+ import { EditorContext } from './contexts';
6
+ import { GoalsLocation, LocationsContext, SelectableLocation } from './goalLocation';
7
+ import { useRpcSession } from './rpcSessions';
8
+ import { WithToggleableTooltip, WithTooltipOnHover } from './tooltips';
9
+ import { mapRpcError, useAsync, useEvent } from './util';
10
+ /**
11
+ * Core loop to display {@link TaggedText} objects. Invokes `InnerTagUi` on `tag` nodes in order to support
12
+ * various embedded information, for example subexpression information stored in {@link CodeWithInfos}.
13
+ * */
14
+ export function InteractiveTaggedText({ fmt, InnerTagUi }) {
15
+ if ('text' in fmt)
16
+ return _jsx(_Fragment, { children: fmt.text });
17
+ else if ('append' in fmt)
18
+ return (_jsx(_Fragment, { children: fmt.append.map((a, i) => (_jsx(InteractiveTaggedText, { fmt: a, InnerTagUi: InnerTagUi }, i))) }));
19
+ else if ('tag' in fmt)
20
+ return _jsx(InnerTagUi, { fmt: fmt.tag[1], tag: fmt.tag[0] });
21
+ else
22
+ throw new Error(`malformed 'TaggedText': '${fmt}'`);
23
+ }
24
+ function Markdown({ contents }) {
25
+ const renderer = new marked.Renderer();
26
+ renderer.code = (code, lang) => {
27
+ // todo: render Lean code blocks using the lean syntax.json
28
+ return `<div class="font-code pre-wrap">${code}</div>`;
29
+ };
30
+ renderer.codespan = code => {
31
+ return `<code class="font-code">${code}</code>`;
32
+ };
33
+ const markedOptions = {};
34
+ markedOptions.sanitizer = (html) => {
35
+ return '';
36
+ };
37
+ markedOptions.sanitize = true;
38
+ markedOptions.silent = true;
39
+ markedOptions.renderer = renderer;
40
+ // todo: vscode also has lots of post render sanitization and hooking up of href clicks and so on.
41
+ // see https://github.com/microsoft/vscode/blob/main/src/vs/base/browser/markdownRenderer.ts
42
+ const renderedMarkdown = marked.parse(contents, markedOptions);
43
+ return _jsx("div", { dangerouslySetInnerHTML: { __html: renderedMarkdown } });
44
+ // handy for debugging:
45
+ // return <div>{ renderedMarkdown } </div>
46
+ }
47
+ /** Shows `explicitValue : itsType` and a docstring if there is one. */
48
+ function TypePopupContents({ info }) {
49
+ const rs = useRpcSession();
50
+ // When `err` is defined we show the error,
51
+ // otherwise if `ip` is defined we show its contents,
52
+ // otherwise a 'loading' message.
53
+ const interactive = useAsync(() => InteractiveDiagnostics_infoToInteractive(rs, info.info), [rs, info.info]);
54
+ // Even when subexpressions are selectable in our parent component, it doesn't make sense
55
+ // to select things inside the *type* of the parent, so we clear the context.
56
+ // NOTE: selecting in the explicit term does make sense but it complicates the implementation
57
+ // so let's not add it until someone really wants it.
58
+ return (_jsx(LocationsContext.Provider, { value: undefined, children: _jsx("div", { className: "tooltip-code-content", children: interactive.state === 'resolved' ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "font-code tl pre-wrap", children: [interactive.value.exprExplicit && _jsx(InteractiveCode, { fmt: interactive.value.exprExplicit }), ' ', ": ", interactive.value.type && _jsx(InteractiveCode, { fmt: interactive.value.type })] }), interactive.value.doc && (_jsxs(_Fragment, { children: [_jsx("hr", {}), _jsx(Markdown, { contents: interactive.value.doc })] })), info.diffStatus && (_jsxs(_Fragment, { children: [_jsx("hr", {}), _jsx("div", { children: DIFF_TAG_TO_EXPLANATION[info.diffStatus] })] }))] })) : interactive.state === 'rejected' ? (_jsxs(_Fragment, { children: ["Error: ", mapRpcError(interactive.error).message] })) : (_jsx(_Fragment, { children: "Loading.." })) }) }));
59
+ }
60
+ const DIFF_TAG_TO_CLASS = {
61
+ wasChanged: 'inserted-text',
62
+ willChange: 'removed-text',
63
+ wasInserted: 'inserted-text',
64
+ willInsert: 'inserted-text',
65
+ willDelete: 'removed-text',
66
+ wasDeleted: 'removed-text',
67
+ };
68
+ const DIFF_TAG_TO_EXPLANATION = {
69
+ wasChanged: 'This subexpression has been modified.',
70
+ willChange: 'This subexpression will be modified.',
71
+ wasInserted: 'This subexpression has been inserted.',
72
+ willInsert: 'This subexpression will be inserted.',
73
+ wasDeleted: 'This subexpression has been removed.',
74
+ willDelete: 'This subexpression will be deleted.',
75
+ };
76
+ /**
77
+ * Tagged spans can be hovered over to display extra info stored in the associated `SubexprInfo`.
78
+ * Moreover if this component is rendered in a context where locations can be selected, the span
79
+ * can be shift-clicked to select it.
80
+ */
81
+ function InteractiveCodeTag({ tag: ct, fmt }) {
82
+ const rs = useRpcSession();
83
+ const ec = React.useContext(EditorContext);
84
+ const [hoverState, setHoverState] = React.useState('off');
85
+ const [goToDefErrorState, setGoToDefErrorState] = React.useState(false);
86
+ // We mimick the VSCode ctrl-hover and ctrl-click UI for go-to-definition
87
+ const [goToLoc, setGoToLoc] = React.useState(undefined);
88
+ const fetchGoToLoc = React.useCallback(async () => {
89
+ if (goToLoc !== undefined)
90
+ return goToLoc;
91
+ try {
92
+ const lnks = await getGoToLocation(rs, 'definition', ct.info);
93
+ if (lnks.length > 0) {
94
+ const loc = { uri: lnks[0].targetUri, range: lnks[0].targetSelectionRange };
95
+ setGoToLoc(loc);
96
+ return loc;
97
+ }
98
+ }
99
+ catch (e) {
100
+ console.error('Error in go-to-definition: ', JSON.stringify(e));
101
+ }
102
+ return undefined;
103
+ }, [rs, ct.info, goToLoc]);
104
+ // Eagerly fetch the location as soon as the pointer enters this area so that we can show
105
+ // an underline if a jump target is available.
106
+ React.useEffect(() => {
107
+ if (hoverState === 'ctrlOver')
108
+ void fetchGoToLoc();
109
+ }, [hoverState, fetchGoToLoc]);
110
+ const execGoToLoc = React.useCallback(async (withError) => {
111
+ const loc = await fetchGoToLoc();
112
+ if (loc === undefined) {
113
+ if (withError) {
114
+ setGoToDefErrorState(true);
115
+ }
116
+ return;
117
+ }
118
+ await ec.revealPosition({ uri: loc.uri, ...loc.range.start });
119
+ }, [fetchGoToLoc, ec]);
120
+ const locs = React.useContext(LocationsContext);
121
+ const ourLoc = locs && locs.subexprTemplate && ct.subexprPos
122
+ ? GoalsLocation.withSubexprPos(locs.subexprTemplate, ct.subexprPos)
123
+ : undefined;
124
+ let spanClassName = hoverState === 'ctrlOver' && goToLoc !== undefined ? 'underline ' : '';
125
+ if (ct.diffStatus) {
126
+ spanClassName += DIFF_TAG_TO_CLASS[ct.diffStatus] + ' ';
127
+ }
128
+ // ID that we can use to identify the component that a context menu was opened in.
129
+ // When selecting a custom context menu entry, VS Code will execute a VS Code command
130
+ // parameterized with `data-vscode-context`. We then use this context to execute the
131
+ // command in the context of the correct interactive code tag in the InfoView.
132
+ const interactiveCodeTagId = React.useId();
133
+ const vscodeContext = { interactiveCodeTagId };
134
+ useEvent(ec.events.goToDefinition, async (_) => void execGoToLoc(true), [execGoToLoc], interactiveCodeTagId);
135
+ return (_jsx(WithToggleableTooltip, { tooltipChildren: `No definition found for '${TaggedText_stripTags(fmt)}'`, isTooltipShown: goToDefErrorState, hideTooltip: () => setGoToDefErrorState(false), children: _jsx(WithTooltipOnHover, { "data-vscode-context": JSON.stringify(vscodeContext), "data-has-tooltip-on-hover": true, tooltipChildren: _jsx(TypePopupContents, { info: ct }), onClick: (e, next) => {
136
+ // On ctrl-click or ⌘-click, if location is known, go to it in the editor
137
+ if (e.ctrlKey || e.metaKey) {
138
+ setHoverState(st => (st === 'over' ? 'ctrlOver' : st));
139
+ void execGoToLoc(false);
140
+ }
141
+ else if (!e.shiftKey)
142
+ next(e);
143
+ }, onContextMenu: e => {
144
+ // Mark the event as seen so that parent handlers skip it.
145
+ // We cannot use `stopPropagation` as that prevents the VSC context menu from showing up.
146
+ if ('_InteractiveCodeTagSeen' in e)
147
+ return;
148
+ e._InteractiveCodeTagSeen = {};
149
+ if (!(e.target instanceof Node))
150
+ return;
151
+ if (!e.currentTarget.contains(e.target))
152
+ return;
153
+ // Select the pretty-printed code.
154
+ const sel = window.getSelection();
155
+ if (!sel)
156
+ return;
157
+ sel.removeAllRanges();
158
+ sel.selectAllChildren(e.currentTarget);
159
+ }, children: _jsx(SelectableLocation, { setHoverState: setHoverState, className: spanClassName, locs: locs, loc: ourLoc, alwaysHighlight: true, children: _jsx(InteractiveCode, { fmt: fmt }) }) }) }));
160
+ }
161
+ /** Displays a {@link CodeWithInfos} obtained via RPC from the Lean server. */
162
+ export function InteractiveCode(props) {
163
+ return (_jsx("span", { className: "font-code", children: _jsx(InteractiveTaggedText, { ...props, InnerTagUi: InteractiveCodeTag }) }));
164
+ }
@@ -0,0 +1,13 @@
1
+ import '@vscode/codicons/dist/codicon.css';
2
+ import '@vscode/codicons/dist/codicon.ttf';
3
+ import 'tachyons/css/tachyons.css';
4
+ import './index.css';
5
+ import { EditorApi, InfoviewApi } from '@leanprover/infoview-api';
6
+ /**
7
+ * Render the Lean infoview into the DOM element `uiElement`.
8
+ *
9
+ * @param editorApi is a collection of methods which the infoview needs to be able to invoke
10
+ * on the editor in order to function correctly (such as inserting text or moving the cursor).
11
+ * @returns a collection of methods which must be invoked when the relevant editor events occur.
12
+ */
13
+ export declare function renderInfoview(editorApi: EditorApi, uiElement: HTMLElement): InfoviewApi;
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { VSCodeButton } from '@vscode/webview-ui-toolkit/react';
3
+ import * as React from 'react';
4
+ import * as ReactDOM from 'react-dom/client';
5
+ import '@vscode/codicons/dist/codicon.css';
6
+ import '@vscode/codicons/dist/codicon.ttf';
7
+ import 'tachyons/css/tachyons.css';
8
+ import './index.css';
9
+ import { defaultInfoviewConfig, } from '@leanprover/infoview-api';
10
+ import { ConfigContext, EditorContext, ProgressContext, VersionContext } from './contexts';
11
+ import { EditorConnection } from './editorConnection';
12
+ import { EventEmitter } from './event';
13
+ import { Infos } from './infos';
14
+ import { AllMessages, WithLspDiagnosticsContext } from './messages';
15
+ import { WithRpcSessions } from './rpcSessions';
16
+ import { ServerVersion } from './serverVersion';
17
+ import { useClientNotificationEffect, useEventResult, useServerNotificationState } from './util';
18
+ function Main(props) {
19
+ const ec = React.useContext(EditorContext);
20
+ /* Set up updates to the global infoview state on editor events. */
21
+ const config = useEventResult(ec.events.changedInfoviewConfig) ?? defaultInfoviewConfig;
22
+ const [allProgress, _1] = useServerNotificationState('$/lean/fileProgress', new Map(), async (params) => allProgress => {
23
+ const newProgress = new Map(allProgress);
24
+ return newProgress.set(params.textDocument.uri, params.processing);
25
+ }, []);
26
+ const curUri = useEventResult(ec.events.changedCursorLocation, loc => loc?.uri);
27
+ useClientNotificationEffect('textDocument/didClose', (params) => {
28
+ if (ec.events.changedCursorLocation.current &&
29
+ ec.events.changedCursorLocation.current.uri === params.textDocument.uri) {
30
+ ec.events.changedCursorLocation.fire(undefined);
31
+ }
32
+ }, []);
33
+ const serverVersion = useEventResult(ec.events.serverRestarted, result => new ServerVersion(result.serverInfo?.version ?? ''));
34
+ const serverStoppedResult = useEventResult(ec.events.serverStopped);
35
+ // NB: the cursor may temporarily become `undefined` when a file is closed. In this case
36
+ // it's important not to reconstruct the `WithBlah` wrappers below since they contain state
37
+ // that we want to persist.
38
+ let ret;
39
+ if (!serverVersion) {
40
+ ret = _jsx("p", { children: "Waiting for Lean server to start..." });
41
+ }
42
+ else if (serverStoppedResult) {
43
+ ret = (_jsxs("div", { children: [_jsx("p", { children: serverStoppedResult.message }), _jsx("p", { className: "error", children: serverStoppedResult.reason })] }));
44
+ }
45
+ else {
46
+ ret = (_jsxs("div", { className: "ma1", children: [_jsx(Infos, {}), curUri && (_jsx("div", { className: "mv2", children: _jsx(AllMessages, { uri: curUri }) })), curUri && (_jsx(VSCodeButton, { className: "restart-file-button", onClick: _ => ec.api.restartFile(curUri), title: "Restarts this file, rebuilding all of its outdated dependencies.", children: "Restart File" }))] }));
47
+ }
48
+ return (_jsx(ConfigContext.Provider, { value: config, children: _jsx(VersionContext.Provider, { value: serverVersion, children: _jsx(WithRpcSessions, { children: _jsx(WithLspDiagnosticsContext, { children: _jsx(ProgressContext.Provider, { value: allProgress, children: ret }) }) }) }) }));
49
+ }
50
+ /**
51
+ * Render the Lean infoview into the DOM element `uiElement`.
52
+ *
53
+ * @param editorApi is a collection of methods which the infoview needs to be able to invoke
54
+ * on the editor in order to function correctly (such as inserting text or moving the cursor).
55
+ * @returns a collection of methods which must be invoked when the relevant editor events occur.
56
+ */
57
+ export function renderInfoview(editorApi, uiElement) {
58
+ const editorEvents = {
59
+ initialize: new EventEmitter(),
60
+ gotServerNotification: new EventEmitter(),
61
+ sentClientNotification: new EventEmitter(),
62
+ serverRestarted: new EventEmitter(),
63
+ serverStopped: new EventEmitter(),
64
+ changedCursorLocation: new EventEmitter(),
65
+ changedInfoviewConfig: new EventEmitter(),
66
+ runTestScript: new EventEmitter(),
67
+ requestedAction: new EventEmitter(),
68
+ goToDefinition: new EventEmitter(),
69
+ };
70
+ // Challenge: write a type-correct fn from `Eventify<T>` to `T` without using `any`
71
+ const infoviewApi = {
72
+ initialize: async (l) => editorEvents.initialize.fire(l),
73
+ gotServerNotification: async (method, params) => {
74
+ editorEvents.gotServerNotification.fire([method, params]);
75
+ },
76
+ sentClientNotification: async (method, params) => {
77
+ editorEvents.sentClientNotification.fire([method, params]);
78
+ },
79
+ serverRestarted: async (r) => editorEvents.serverRestarted.fire(r),
80
+ serverStopped: async (serverStoppedReason) => {
81
+ editorEvents.serverStopped.fire(serverStoppedReason);
82
+ },
83
+ changedCursorLocation: async (loc) => editorEvents.changedCursorLocation.fire(loc),
84
+ changedInfoviewConfig: async (conf) => editorEvents.changedInfoviewConfig.fire(conf),
85
+ requestedAction: async (action) => editorEvents.requestedAction.fire(action, action.kind),
86
+ goToDefinition: async (id) => editorEvents.goToDefinition.fire(id, id),
87
+ // See https://rollupjs.org/guide/en/#avoiding-eval
88
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
89
+ runTestScript: async (script) => new Function(script)(),
90
+ getInfoviewHtml: async () => document.body.innerHTML,
91
+ };
92
+ const ec = new EditorConnection(editorApi, editorEvents);
93
+ editorEvents.initialize.on((loc) => ec.events.changedCursorLocation.fire(loc));
94
+ const root = ReactDOM.createRoot(uiElement);
95
+ root.render(_jsx(React.StrictMode, { children: _jsx(EditorContext.Provider, { value: ec, children: _jsx(Main, {}) }) }));
96
+ return infoviewApi;
97
+ }
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { Diagnostic, DocumentUri } from 'vscode-languageserver-protocol';
3
+ import { InteractiveDiagnostic } from '@leanprover/infoview-api';
4
+ /** Shows the given messages assuming they are for the given file. */
5
+ export declare const MessagesList: any;
6
+ /** Displays all messages for the specified file. Can be paused. */
7
+ export declare function AllMessages({ uri: uri0 }: {
8
+ uri: DocumentUri;
9
+ }): any;
10
+ /**
11
+ * Provides a `LspDiagnosticsContext` which stores the latest version of the
12
+ * diagnostics as sent by the publishDiagnostics notification.
13
+ */
14
+ export declare function WithLspDiagnosticsContext({ children }: React.PropsWithChildren<{}>): any;
15
+ /** Embeds a non-interactive diagnostic into the type `InteractiveDiagnostic`. */
16
+ export declare function lspDiagToInteractive(diag: Diagnostic): InteractiveDiagnostic;
@@ -0,0 +1,151 @@
1
+ import { createElement as _createElement } from "react";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as React from 'react';
4
+ import fastIsEqual from 'react-fast-compare';
5
+ import { DiagnosticSeverity, } from 'vscode-languageserver-protocol';
6
+ import { RpcErrorCode } from '@leanprover/infoview-api';
7
+ import { getInteractiveDiagnostics } from '@leanprover/infoview-api';
8
+ import { Details } from './collapsing';
9
+ import { ConfigContext, EditorContext, EnvPosContext, LspDiagnosticsContext } from './contexts';
10
+ import { RpcContext, useRpcSessionAtPos } from './rpcSessions';
11
+ import { InteractiveMessage } from './traceExplorer';
12
+ import { addUniqueKeys, basename, DocumentPosition, escapeHtml, useEvent, usePausableState, useServerNotificationState, } from './util';
13
+ const MessageView = React.memo(({ uri, diag }) => {
14
+ const ec = React.useContext(EditorContext);
15
+ const fname = escapeHtml(basename(uri));
16
+ const { line, character } = diag.range.start;
17
+ const loc = { uri, range: diag.range };
18
+ /* We grab the text contents of the message from `node.innerText`. */
19
+ const node = React.useRef(null);
20
+ const severityClass = diag.severity
21
+ ? {
22
+ [DiagnosticSeverity.Error]: 'error',
23
+ [DiagnosticSeverity.Warning]: 'warning',
24
+ [DiagnosticSeverity.Information]: 'information',
25
+ [DiagnosticSeverity.Hint]: 'hint',
26
+ }[diag.severity]
27
+ : '';
28
+ const title = `${fname}:${line + 1}:${character}`;
29
+ const startPos = React.useMemo(() => ({ uri, ...(diag.fullRange?.start || diag.range.start) }), [uri, diag.fullRange, diag.range]);
30
+ return (_jsxs("details", { open: true, children: [_jsxs("summary", { className: severityClass + ' mv2 pointer', children: [title, _jsxs("span", { className: "fr", onClick: e => e.preventDefault(), children: [_jsx("a", { className: "link pointer mh2 dim codicon codicon-go-to-file", onClick: _ => {
31
+ void ec.revealLocation(loc);
32
+ }, title: "reveal file location" }), _jsx("a", { className: "link pointer mh2 dim codicon codicon-quote", "data-id": "copy-to-comment", onClick: _ => {
33
+ if (node.current)
34
+ void ec.copyToComment(node.current.innerText);
35
+ }, title: "copy message to comment" }), _jsx("a", { className: "link pointer mh2 dim codicon codicon-clippy", onClick: _ => {
36
+ if (node.current)
37
+ void ec.api.copyToClipboard(node.current.innerText);
38
+ }, title: "copy message to clipboard" })] })] }), _jsx("div", { className: "ml1", ref: node, children: _jsx("pre", { className: "font-code pre-wrap", children: _jsx(EnvPosContext.Provider, { value: startPos, children: _jsx(InteractiveMessage, { fmt: diag.message }) }) }) })] }));
39
+ }, fastIsEqual);
40
+ function mkMessageViewProps(uri, messages) {
41
+ const views = messages
42
+ .sort((msga, msgb) => {
43
+ const a = msga.fullRange?.end || msga.range.end;
44
+ const b = msgb.fullRange?.end || msgb.range.end;
45
+ return a.line === b.line ? a.character - b.character : a.line - b.line;
46
+ })
47
+ .map(m => {
48
+ return { uri, diag: m };
49
+ });
50
+ return addUniqueKeys(views, v => DocumentPosition.toString({ uri: v.uri, ...v.diag.range.start }));
51
+ }
52
+ /** Shows the given messages assuming they are for the given file. */
53
+ export const MessagesList = React.memo(({ uri, messages }) => {
54
+ const should_hide = messages.length === 0;
55
+ if (should_hide) {
56
+ return _jsx(_Fragment, { children: "No messages." });
57
+ }
58
+ return (_jsx("div", { className: "ml1", children: mkMessageViewProps(uri, messages).map(m => (_createElement(MessageView, { ...m, key: m.key }))) }));
59
+ });
60
+ function lazy(f) {
61
+ let state;
62
+ return () => {
63
+ if (!state)
64
+ state = { t: f() };
65
+ return state.t;
66
+ };
67
+ }
68
+ /** Displays all messages for the specified file. Can be paused. */
69
+ export function AllMessages({ uri: uri0 }) {
70
+ const ec = React.useContext(EditorContext);
71
+ const rs0 = useRpcSessionAtPos({ uri: uri0, line: 0, character: 0 });
72
+ const dc = React.useContext(LspDiagnosticsContext);
73
+ const config = React.useContext(ConfigContext);
74
+ const diags0 = React.useMemo(() => dc.get(uri0) || [], [dc, uri0]);
75
+ const iDiags0 = React.useMemo(() => lazy(async () => {
76
+ // The last line for which we have received diagnostics so far.
77
+ // Providing a line range to `getInteractiveDiagnostics`
78
+ // ensures that the call doesn't block until the whole file is elaborated.
79
+ const maxLine = diags0.reduce((ln, d) => Math.max(ln, d.range.end.line), 0) + 1;
80
+ try {
81
+ const diags = await getInteractiveDiagnostics(rs0, { start: 0, end: maxLine });
82
+ if (diags.length > 0) {
83
+ return diags;
84
+ }
85
+ }
86
+ catch (err) {
87
+ if (err?.code === RpcErrorCode.ContentModified) {
88
+ // Document has been changed since we made the request. This can happen
89
+ // while typing quickly. When the server catches up on next edit, it will
90
+ // send new diagnostics to which the infoview responds by calling
91
+ // `getInteractiveDiagnostics` again.
92
+ }
93
+ else {
94
+ console.log('getInteractiveDiagnostics error ', err);
95
+ }
96
+ }
97
+ return diags0.map(d => ({ ...d, message: { text: d.message } }));
98
+ }), [rs0, diags0]);
99
+ const [{ isPaused, setPaused }, [uri, rs, diags, iDiags], _] = usePausableState(false, [uri0, rs0, diags0, iDiags0]);
100
+ // Fetch interactive diagnostics when we're entering the paused state
101
+ // (if they haven't already been fetched before)
102
+ React.useEffect(() => {
103
+ if (isPaused) {
104
+ void iDiags();
105
+ }
106
+ }, [iDiags, isPaused]);
107
+ const setOpenRef = React.useRef();
108
+ useEvent(ec.events.requestedAction, _ => {
109
+ if (setOpenRef.current !== undefined) {
110
+ setOpenRef.current(t => !t);
111
+ }
112
+ }, [setOpenRef], 'toggleAllMessages');
113
+ // The number of actually displayed messages, or `undefined` if the panel is collapsed.
114
+ // When `undefined`, we can approximate it by `diags.length`.
115
+ const [numDiags, setNumDiags] = React.useState(undefined);
116
+ return (_jsx(RpcContext.Provider, { value: rs, children: _jsxs(Details, { setOpenRef: r => (setOpenRef.current = r), initiallyOpen: !config.autoOpenShowsGoal, children: [_jsxs("summary", { className: "mv2 pointer", children: ["All Messages (", numDiags ?? diags.length, ")", _jsx("span", { className: "fr", onClick: e => {
117
+ e.preventDefault();
118
+ }, children: _jsx("a", { className: 'link pointer mh2 dim codicon ' +
119
+ (isPaused ? 'codicon-debug-continue' : 'codicon-debug-pause'), onClick: _ => {
120
+ setPaused(p => !p);
121
+ }, title: isPaused ? 'continue updating' : 'pause updating' }) })] }), _jsx(AllMessagesBody, { uri: uri, messages: iDiags, setNumDiags: setNumDiags })] }) }));
122
+ }
123
+ /** We factor out the body of {@link AllMessages} which lazily fetches its contents only when expanded. */
124
+ function AllMessagesBody({ uri, messages, setNumDiags }) {
125
+ const [msgs, setMsgs] = React.useState(undefined);
126
+ React.useEffect(() => {
127
+ const fn = async () => {
128
+ const msgs = await messages();
129
+ setMsgs(msgs);
130
+ setNumDiags(msgs.length);
131
+ };
132
+ void fn();
133
+ }, [messages, setNumDiags]);
134
+ React.useEffect(() => () => /* Called on unmount. */ setNumDiags(undefined), [setNumDiags]);
135
+ if (msgs === undefined)
136
+ return _jsx(_Fragment, { children: "Loading messages..." });
137
+ else
138
+ return _jsx(MessagesList, { uri: uri, messages: msgs });
139
+ }
140
+ /**
141
+ * Provides a `LspDiagnosticsContext` which stores the latest version of the
142
+ * diagnostics as sent by the publishDiagnostics notification.
143
+ */
144
+ export function WithLspDiagnosticsContext({ children }) {
145
+ const [allDiags, _0] = useServerNotificationState('textDocument/publishDiagnostics', new Map(), async (params) => diags => new Map(diags).set(params.uri, params.diagnostics), []);
146
+ return _jsx(LspDiagnosticsContext.Provider, { value: allDiags, children: children });
147
+ }
148
+ /** Embeds a non-interactive diagnostic into the type `InteractiveDiagnostic`. */
149
+ export function lspDiagToInteractive(diag) {
150
+ return { ...diag, message: { text: diag.message } };
151
+ }
@@ -0,0 +1,21 @@
1
+ import { RpcSessionAtPos } from '@leanprover/infoview-api';
2
+ import * as React from 'react';
3
+ import type { TextDocumentPositionParams } from 'vscode-languageserver-protocol';
4
+ import { DocumentPosition } from './util';
5
+ /**
6
+ * Provides a {@link RpcSessionsContext} to the children.
7
+ * The {@link RpcSessions} object stored there manages RPC sessions in the Lean server.
8
+ */
9
+ export declare function WithRpcSessions({ children }: {
10
+ children: React.ReactNode;
11
+ }): any;
12
+ export declare function useRpcSessionAtTdpp(pos: TextDocumentPositionParams): RpcSessionAtPos;
13
+ export declare function useRpcSessionAtPos(pos: DocumentPosition): RpcSessionAtPos;
14
+ /** @deprecated use {@link useRpcSession} instead */
15
+ export declare const RpcContext: any;
16
+ /**
17
+ * Retrieve an RPC session at {@link EnvPosContext},
18
+ * if the context is set.
19
+ * Otherwise return a dummy session that throws on any RPC call.
20
+ */
21
+ export declare function useRpcSession(): RpcSessionAtPos;
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { RpcSessions } from '@leanprover/infoview-api';
3
+ import * as React from 'react';
4
+ import { EditorContext, EnvPosContext } from './contexts';
5
+ import { DocumentPosition, useClientNotificationEffect, useEvent } from './util';
6
+ const RpcSessionsContext = React.createContext(undefined);
7
+ /**
8
+ * Provides a {@link RpcSessionsContext} to the children.
9
+ * The {@link RpcSessions} object stored there manages RPC sessions in the Lean server.
10
+ */
11
+ export function WithRpcSessions({ children }) {
12
+ const ec = React.useContext(EditorContext);
13
+ const [sessions] = React.useState(() => new RpcSessions({
14
+ createRpcSession: (uri) => ec.api.createRpcSession(uri),
15
+ closeRpcSession: (uri) => ec.api.closeRpcSession(uri),
16
+ call: (params) => ec.api.sendClientRequest(params.textDocument.uri, '$/lean/rpc/call', params),
17
+ release: (params) => void ec.api.sendClientNotification(params.uri, '$/lean/rpc/release', params),
18
+ }));
19
+ React.useEffect(() => {
20
+ // Clean up the sessions on unmount
21
+ return () => sessions.dispose();
22
+ }, [sessions]);
23
+ useClientNotificationEffect('textDocument/didClose', (params) => {
24
+ sessions.closeSessionForFile(params.textDocument.uri);
25
+ }, [sessions]);
26
+ // TODO: only restart files for the server that stopped
27
+ useEvent(ec.events.serverRestarted, () => sessions.closeAllSessions());
28
+ return _jsx(RpcSessionsContext.Provider, { value: sessions, children: children });
29
+ }
30
+ const noCtxRpcSession = {
31
+ call: async () => {
32
+ throw new Error('no RPC context set');
33
+ },
34
+ };
35
+ const noPosRpcSession = {
36
+ call: async () => {
37
+ throw new Error('no position context set');
38
+ },
39
+ };
40
+ export function useRpcSessionAtTdpp(pos) {
41
+ return React.useContext(RpcSessionsContext)?.connect(pos) || noCtxRpcSession;
42
+ }
43
+ export function useRpcSessionAtPos(pos) {
44
+ return useRpcSessionAtTdpp(DocumentPosition.toTdpp(pos));
45
+ }
46
+ /** @deprecated use {@link useRpcSession} instead */
47
+ /*
48
+ * NOTE(WN): This context cannot be removed as of 2024-05-27 since existing widgets use it.
49
+ * For backwards compatibility, it must be set to the correct value by infoview code.
50
+ * A future major release of @leanprover/infoview could remove this context
51
+ * after it has been deprecated for a sufficiently long time.
52
+ */
53
+ export const RpcContext = React.createContext(noCtxRpcSession);
54
+ /**
55
+ * Retrieve an RPC session at {@link EnvPosContext},
56
+ * if the context is set.
57
+ * Otherwise return a dummy session that throws on any RPC call.
58
+ */
59
+ export function useRpcSession() {
60
+ const pos = React.useContext(EnvPosContext);
61
+ const rsc = React.useContext(RpcSessionsContext);
62
+ if (!pos)
63
+ return noPosRpcSession;
64
+ if (!rsc)
65
+ return noCtxRpcSession;
66
+ return rsc.connect(DocumentPosition.toTdpp(pos));
67
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Keeps track of the Lean server version and available features.
3
+ * @module
4
+ */
5
+ export declare class ServerVersion {
6
+ major: number;
7
+ minor: number;
8
+ patch: number;
9
+ constructor(version: string);
10
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Keeps track of the Lean server version and available features.
3
+ * @module
4
+ */
5
+ export class ServerVersion {
6
+ major;
7
+ minor;
8
+ patch;
9
+ constructor(version) {
10
+ const vs = version.split('.');
11
+ if (vs.length === 2) {
12
+ this.major = parseInt(vs[0]);
13
+ this.minor = parseInt(vs[1]);
14
+ this.patch = 0;
15
+ }
16
+ else if (vs.length === 3) {
17
+ this.major = parseInt(vs[0]);
18
+ this.minor = parseInt(vs[1]);
19
+ this.patch = parseInt(vs[2]);
20
+ }
21
+ else {
22
+ throw new Error(`cannot parse Lean server version '${version}'`);
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,23 @@
1
+ import * as React from 'react';
2
+ export type TooltipProps = React.PropsWithChildren<React.HTMLProps<HTMLDivElement>> & {
3
+ reference: HTMLElement | null;
4
+ };
5
+ export declare function Tooltip(props_: TooltipProps): any;
6
+ export declare const WithToggleableTooltip: React.ForwardRefExoticComponent<any>;
7
+ /** Hover state of an element. The pointer can be
8
+ * - elsewhere (`off`)
9
+ * - over the element (`over`)
10
+ * - over the element with Ctrl or Meta (⌘ on Mac) held (`ctrlOver`)
11
+ */
12
+ export type HoverState = 'off' | 'over' | 'ctrlOver';
13
+ /** An element which calls `setHoverState` when the hover state of its DOM children changes.
14
+ *
15
+ * It is implemented with JS rather than CSS in order to allow nesting of these elements. When nested,
16
+ * only the smallest (deepest in the DOM tree) {@link DetectHoverSpan} has an enabled hover state. */
17
+ export declare const DetectHoverSpan: React.ForwardRefExoticComponent<any>;
18
+ /** Shows a tooltip when the children are hovered over or clicked.
19
+ *
20
+ * An `onClick` middleware can optionally be given in order to control what happens when the
21
+ * hoverable area is clicked. The middleware can invoke `next` to execute the default action
22
+ * which is to pin the tooltip open. */
23
+ export declare const WithTooltipOnHover: React.ForwardRefExoticComponent<any>;