lean4monaco 1.1.9 → 1.1.11

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './editor';
2
2
  export * from './leanmonaco';
3
3
  export { RpcSessionAtPos } from './vscode-lean4/vscode-lean4/src/infoview';
4
+ export { DocumentPosition } from './vscode-lean4/lean4-infoview/src/infoview/util';
4
5
  export { LeanClient } from './vscode-lean4/vscode-lean4/src/leanclient';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './editor';
2
2
  export * from './leanmonaco';
3
3
  export { RpcSessionAtPos } from './vscode-lean4/vscode-lean4/src/infoview';
4
+ export { DocumentPosition } from './vscode-lean4/lean4-infoview/src/infoview/util';
4
5
  export { LeanClient } from './vscode-lean4/vscode-lean4/src/leanclient';
@@ -152,7 +152,7 @@ export class LeanMonaco {
152
152
  "editor.fontFamily": "'Noto Color Emoji', 'JuliaMono'",
153
153
  "editor.wordWrap": "on",
154
154
  "editor.wrappingStrategy": "advanced",
155
- "workbench.colorTheme": "Visual Studio Light",
155
+ "workbench.colorTheme": isBrowserDefaultDark() ? "Visual Studio Dark" : "Visual Studio Light",
156
156
  ...options.vscode
157
157
  });
158
158
  if (this.disposed) {
@@ -253,3 +253,7 @@ export class LeanMonaco {
253
253
  this.abbreviationFeature = undefined;
254
254
  }
255
255
  }
256
+ /** Returns true if the browser wants dark mode */
257
+ function isBrowserDefaultDark() {
258
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
259
+ }
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+ import { InfoviewConfig, LeanDiagnostic, LeanFileProgressProcessingInfo } from '@leanprover/infoview-api';
3
+ import { EditorConnection } from './editorConnection';
4
+ import { ServerVersion } from './serverVersion';
5
+ import { DocumentPosition } from './util';
6
+ export declare const EditorContext: React.Context<EditorConnection>;
7
+ export declare const VersionContext: React.Context<ServerVersion | undefined>;
8
+ export declare const ConfigContext: React.Context<InfoviewConfig>;
9
+ export declare const LspDiagnosticsContext: React.Context<Map<string, LeanDiagnostic[]>>;
10
+ export declare const ProgressContext: React.Context<Map<string, LeanFileProgressProcessingInfo[]>>;
11
+ /**
12
+ * Certain infoview components and widget instances
13
+ * utilize data that has been introduced above a specific position
14
+ * in a Lean source file.
15
+ *
16
+ * For instance, if we declare a global constant with `def foo` on line 10,
17
+ * we can immediately display it in a widget on line 11.
18
+ * To achieve this, the widget code needs to have access
19
+ * to the environment on line 11 as that environment contains `foo`.
20
+ *
21
+ * {@link EnvPosContext} stores the position in the file
22
+ * from which an appropriate environment can be retrieved.
23
+ * This is used to look up global constants,
24
+ * in particular RPC methods (`@[server_rpc_method]`)
25
+ * and widget modules (`@[widget_module]`).
26
+ *
27
+ * Note that {@link EnvPosContext} may, but need not,
28
+ * be equal to any of the positions which the infoview keeps track of
29
+ * (such as the editor cursor position).
30
+ *
31
+ * #### Infoview implementation details
32
+ *
33
+ * In the infoview, {@link EnvPosContext} is set as follows:
34
+ * - in an `<InfoDisplay>`,
35
+ * it is the position at which the info block is being displayed:
36
+ * either a recent editor cursor position
37
+ * (when shown in the at-cursor `<InfoDisplay>`,
38
+ * this will lag behind the current editor cursor position
39
+ * while the `<InfoDisplay>` is in the process of updating),
40
+ * or a pinned position.
41
+ * - in an `<InteractiveMessage>` that comes from a diagnostic
42
+ * emitted with a syntactic range,
43
+ * it is the start of the diagnostic's `fullRange`.
44
+ */
45
+ export declare const EnvPosContext: React.Context<DocumentPosition | undefined>;
@@ -0,0 +1,44 @@
1
+ import * as React from 'react';
2
+ import { defaultInfoviewConfig, } from '@leanprover/infoview-api';
3
+ // Type-unsafe initializers for contexts which we immediately set up at the top-level.
4
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
5
+ export const EditorContext = React.createContext(null);
6
+ export const VersionContext = React.createContext(undefined);
7
+ export const ConfigContext = React.createContext(defaultInfoviewConfig);
8
+ export const LspDiagnosticsContext = React.createContext(new Map());
9
+ export const ProgressContext = React.createContext(new Map());
10
+ /**
11
+ * Certain infoview components and widget instances
12
+ * utilize data that has been introduced above a specific position
13
+ * in a Lean source file.
14
+ *
15
+ * For instance, if we declare a global constant with `def foo` on line 10,
16
+ * we can immediately display it in a widget on line 11.
17
+ * To achieve this, the widget code needs to have access
18
+ * to the environment on line 11 as that environment contains `foo`.
19
+ *
20
+ * {@link EnvPosContext} stores the position in the file
21
+ * from which an appropriate environment can be retrieved.
22
+ * This is used to look up global constants,
23
+ * in particular RPC methods (`@[server_rpc_method]`)
24
+ * and widget modules (`@[widget_module]`).
25
+ *
26
+ * Note that {@link EnvPosContext} may, but need not,
27
+ * be equal to any of the positions which the infoview keeps track of
28
+ * (such as the editor cursor position).
29
+ *
30
+ * #### Infoview implementation details
31
+ *
32
+ * In the infoview, {@link EnvPosContext} is set as follows:
33
+ * - in an `<InfoDisplay>`,
34
+ * it is the position at which the info block is being displayed:
35
+ * either a recent editor cursor position
36
+ * (when shown in the at-cursor `<InfoDisplay>`,
37
+ * this will lag behind the current editor cursor position
38
+ * while the `<InfoDisplay>` is in the process of updating),
39
+ * or a pinned position.
40
+ * - in an `<InteractiveMessage>` that comes from a diagnostic
41
+ * emitted with a syntactic range,
42
+ * it is the start of the diagnostic's `fullRange`.
43
+ */
44
+ export const EnvPosContext = React.createContext(undefined);
@@ -0,0 +1,27 @@
1
+ import type { Location } from 'vscode-languageserver-protocol';
2
+ import { ContextMenuAction, EditorApi, InfoviewAction, InfoviewActionKind, InfoviewApi, PlainGoal, PlainTermGoal } from '@leanprover/infoview-api';
3
+ import { EventEmitter, Eventify } from './event';
4
+ import { DocumentPosition } from './util';
5
+ export type EditorEvents = Omit<Eventify<InfoviewApi>, 'requestedAction' | 'clickedContextMenu'> & {
6
+ requestedAction: EventEmitter<InfoviewAction, InfoviewActionKind>;
7
+ /**
8
+ * Must fire whenever the user clicks an infoview-specific context menu entry.
9
+ *
10
+ * Keys for this event are of the form `` `${action.entry}:${action.id}` ``.
11
+ */
12
+ clickedContextMenu: EventEmitter<ContextMenuAction, string>;
13
+ };
14
+ /** Provides higher-level wrappers around functionality provided by the editor,
15
+ * e.g. to insert a comment. See also {@link EditorApi}. */
16
+ export declare class EditorConnection {
17
+ readonly api: EditorApi;
18
+ readonly events: EditorEvents;
19
+ constructor(api: EditorApi, events: EditorEvents);
20
+ /** Highlights the given range in a document in the editor. */
21
+ revealLocation(loc: Location): Promise<void>;
22
+ revealPosition(pos: DocumentPosition): Promise<void>;
23
+ /** Copies the text to a comment at the cursor position. */
24
+ copyToComment(text: string): Promise<void>;
25
+ requestPlainGoal(pos: DocumentPosition): Promise<PlainGoal | undefined>;
26
+ requestPlainTermGoal(pos: DocumentPosition): Promise<PlainTermGoal | undefined>;
27
+ }
@@ -0,0 +1,41 @@
1
+ import { DocumentPosition } from './util';
2
+ /** Provides higher-level wrappers around functionality provided by the editor,
3
+ * e.g. to insert a comment. See also {@link EditorApi}. */
4
+ export class EditorConnection {
5
+ api;
6
+ events;
7
+ constructor(api, events) {
8
+ this.api = api;
9
+ this.events = events;
10
+ }
11
+ /** Highlights the given range in a document in the editor. */
12
+ async revealLocation(loc) {
13
+ const show = {
14
+ uri: loc.uri,
15
+ selection: loc.range,
16
+ };
17
+ await this.api.showDocument(show);
18
+ }
19
+ async revealPosition(pos) {
20
+ const loc = {
21
+ uri: pos.uri,
22
+ range: {
23
+ start: pos,
24
+ end: pos,
25
+ },
26
+ };
27
+ await this.revealLocation(loc);
28
+ }
29
+ /** Copies the text to a comment at the cursor position. */
30
+ async copyToComment(text) {
31
+ await this.api.insertText(`/-\n${text}\n-/`, 'above');
32
+ }
33
+ requestPlainGoal(pos) {
34
+ const params = DocumentPosition.toTdpp(pos);
35
+ return this.api.sendClientRequest(pos.uri, '$/lean/plainGoal', params);
36
+ }
37
+ requestPlainTermGoal(pos) {
38
+ const params = DocumentPosition.toTdpp(pos);
39
+ return this.api.sendClientRequest(pos.uri, '$/lean/plainTermGoal', params);
40
+ }
41
+ }
@@ -0,0 +1,33 @@
1
+ import type { Disposable } from 'vscode-languageserver-protocol';
2
+ export declare class EventEmitter<E, in out K> {
3
+ private freshId;
4
+ private handlers;
5
+ private handlersWithKey;
6
+ current?: E;
7
+ /**
8
+ * Register a handler that will receive events from this emitter
9
+ * and return a closure that removes the handler registration.
10
+ *
11
+ * If `key` is specified, only events fired with that key
12
+ * will be propagated to this handler.
13
+ */
14
+ on(handler: (_: E) => void, key?: K): Disposable;
15
+ /**
16
+ * Propagate the event to registered handlers.
17
+ *
18
+ * The event is propagated to all keyless handlers.
19
+ * Furthermore if `key` is provided,
20
+ * the event is also propagated to handlers registered with that key.
21
+ */
22
+ fire(event: E, key?: K): void;
23
+ }
24
+ type ExcludeNonEvent<T, U> = T extends (...args: any) => Promise<void> ? U : never;
25
+ /**
26
+ * Turn all fields in `T` which extend `(...args: As) => Promise<void>`
27
+ * into event emitter fields `f: EventEmitter<As>`.
28
+ * Other fields are removed.
29
+ */
30
+ export type Eventify<T> = {
31
+ [P in keyof T as ExcludeNonEvent<T[P], P>]: T[P] extends (arg: infer A) => Promise<void> ? EventEmitter<A, never> : T[P] extends (...args: infer As) => Promise<void> ? EventEmitter<As, never> : never;
32
+ };
33
+ export {};
@@ -0,0 +1,57 @@
1
+ export class EventEmitter {
2
+ freshId = 0;
3
+ handlers = new Map();
4
+ handlersWithKey = new Map();
5
+ current;
6
+ /**
7
+ * Register a handler that will receive events from this emitter
8
+ * and return a closure that removes the handler registration.
9
+ *
10
+ * If `key` is specified, only events fired with that key
11
+ * will be propagated to this handler.
12
+ */
13
+ on(handler, key) {
14
+ const id = this.freshId;
15
+ this.freshId += 1;
16
+ if (key) {
17
+ const handlersForKey = this.handlersWithKey.get(key) ?? [];
18
+ handlersForKey.push(handler);
19
+ this.handlersWithKey.set(key, handlersForKey);
20
+ }
21
+ else {
22
+ this.handlers.set(id, handler);
23
+ }
24
+ return {
25
+ dispose: () => {
26
+ if (key) {
27
+ const handlersForKey = this.handlersWithKey.get(key) ?? [];
28
+ // We assume that no key has so many handlers registered
29
+ // that the linear `filter` operation becomes a perf issue.
30
+ this.handlersWithKey.set(key, handlersForKey.filter(h => h !== handler));
31
+ }
32
+ else {
33
+ this.handlers.delete(id);
34
+ }
35
+ },
36
+ };
37
+ }
38
+ /**
39
+ * Propagate the event to registered handlers.
40
+ *
41
+ * The event is propagated to all keyless handlers.
42
+ * Furthermore if `key` is provided,
43
+ * the event is also propagated to handlers registered with that key.
44
+ */
45
+ fire(event, key) {
46
+ this.current = event;
47
+ for (const h of this.handlers.values()) {
48
+ h(event);
49
+ }
50
+ if (key) {
51
+ const handlersForKey = this.handlersWithKey.get(key) ?? [];
52
+ for (const h of handlersForKey) {
53
+ h(event);
54
+ }
55
+ }
56
+ }
57
+ }
@@ -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,141 @@
1
+ import * as React from 'react';
2
+ import type { DocumentUri, Position, Range, TextDocumentPositionParams } from 'vscode-languageserver-protocol';
3
+ import { EventEmitter } from './event';
4
+ /** A document URI and a position in that document. */
5
+ export interface DocumentPosition extends Position {
6
+ uri: DocumentUri;
7
+ }
8
+ export declare namespace DocumentPosition {
9
+ function isEqual(p1: DocumentPosition, p2: DocumentPosition): boolean;
10
+ function toTdpp(p: DocumentPosition): TextDocumentPositionParams;
11
+ function toString(p: DocumentPosition): string;
12
+ }
13
+ export declare namespace PositionHelpers {
14
+ function isLessThanOrEqual(p1: Position, p2: Position): boolean;
15
+ }
16
+ export declare namespace RangeHelpers {
17
+ function contains(range: Range, pos: Position, ignoreCharacter?: boolean): boolean;
18
+ }
19
+ export declare function escapeHtml(s: string): string;
20
+ /** @deprecated (unused) */
21
+ export declare function colorizeMessage(goal: string): string;
22
+ export declare function basename(path: string): string;
23
+ /**
24
+ * A specialization of {@link React.useEffect} which executes `f` with the event data
25
+ * whenever `ev` fires.
26
+ * If `key` is provided, `f` is only invoked on events fired with that key.
27
+ */
28
+ export declare function useEvent<E, K>(ev: EventEmitter<E, K>, f: (_: E) => void, dependencies?: React.DependencyList, key?: K): void;
29
+ /**
30
+ * A piece of React {@link React.useState} which returns the data that `ev` most recently fired with.
31
+ * If `f` is provided, the data is mapped through `f` first. */
32
+ export declare function useEventResult<E, K>(ev: EventEmitter<E, K>): E | undefined;
33
+ export declare function useEventResult<E, K, T>(ev: EventEmitter<E, K>, f: (newVal: E) => T): T | undefined;
34
+ export declare function useServerNotificationEffect<T>(method: string, f: (params: T) => void, deps?: React.DependencyList): void;
35
+ /**
36
+ * Returns the same tuple as `setState` such that whenever a server notification with `method`
37
+ * arrives at the editor, the state will be updated according to `f`.
38
+ */
39
+ export declare function useServerNotificationState<S, T>(method: string, initial: S, f: (params: T) => Promise<(state: S) => S>, deps?: React.DependencyList): [S, React.Dispatch<React.SetStateAction<S>>];
40
+ export declare function useClientNotificationEffect<T>(method: string, f: (params: T) => void, deps?: React.DependencyList): void;
41
+ /**
42
+ * Like {@link useServerNotificationState} but for client->server notifications sent by the editor.
43
+ */
44
+ export declare function useClientNotificationState<S, T>(method: string, initial: S, f: (state: S, params: T) => S, deps?: React.DependencyList): [S, React.Dispatch<React.SetStateAction<S>>];
45
+ /** Useful for controlling {@link usePausableState} from child components. */
46
+ export interface PausableProps {
47
+ isPaused: boolean;
48
+ setPaused: React.Dispatch<React.SetStateAction<boolean>>;
49
+ }
50
+ /**
51
+ * Returns `[{ isPaused, setPaused }, tPausable, tRef]` s.t.
52
+ * - `[isPaused, setPaused]` are the paused status state
53
+ * - for as long as `isPaused` is set, `tPausable` holds its initial value (the `t` passed before pausing)
54
+ * rather than updates with changes to `t`.
55
+ * - `tRef` can be used to overwrite the paused state
56
+ *
57
+ * To pause child components, `startPaused` can be passed in their props.
58
+ */
59
+ export declare function usePausableState<T>(startPaused: boolean, t: T): [PausableProps, T, React.MutableRefObject<T>];
60
+ export type Keyed<T> = T & {
61
+ key: string;
62
+ };
63
+ /**
64
+ * Adds a unique `key` property to each element in `elems` using
65
+ * the values of (possibly non-injective) `getId`.
66
+ */
67
+ export declare function addUniqueKeys<T>(elems: T[], getId: (el: T) => string): Keyed<T>[];
68
+ export interface LogicalDomElement {
69
+ contains(el: Node): boolean;
70
+ }
71
+ export interface LogicalDomStorage {
72
+ /** Registers a descendant in the logical DOM.
73
+ * Returns a function which disposes of the registration. */
74
+ registerDescendant(el: LogicalDomElement): () => void;
75
+ }
76
+ export declare const LogicalDomContext: React.Context<LogicalDomStorage>;
77
+ /** Suppose a component B appears as a React descendant of the component A. For layout reasons,
78
+ * we sometimes don't want B to appear as a descendant of A in the DOM, so we use `createPortal`.
79
+ * We may still however want to carry out `contains` checks as if B were there, i.e. according to
80
+ * the React tree structure rather than the DOM structure. While React already correctly propagates
81
+ * DOM events up the React tree, other functionality such as `contains` is not provided. We provide
82
+ * it in this hook.
83
+ *
84
+ * Accepts a ref to the observed {@link HTMLElement} (A in the example). Returns:
85
+ * - a {@link LogicalDomElement} which provides `contains` checks for that {@link HTMLElement}; and
86
+ * - a {@link LogicalDomStorage} which MUST be passed to a {@link LogicalDomContext} enclosing
87
+ * the observed {@link HTMLElement}.
88
+ *
89
+ * Additionally, any component which introduces a portal MUST call `registerDescendant` in the
90
+ * {@link LogicalDomContext} with a ref to the portalled component (B in the example). */
91
+ export declare function useLogicalDomObserver(elt: React.RefObject<HTMLElement>): [LogicalDomElement, LogicalDomStorage];
92
+ /**
93
+ * An effect which calls `onClickOutside` whenever an element not logically descending from `ld`
94
+ * (see {@link useLogicalDomObserver}) is clicked. Note that `onClickOutside` is not called on clicks
95
+ * on the scrollbar since these should usually not impact the app's state.
96
+ * `isActive` controls whether the `onClickOutside` handler is active. This can be useful for performance,
97
+ * since having lots of `onClickOutside` handlers when they are not needed can be expensive.
98
+ */
99
+ export declare function useOnClickOutside(ld: LogicalDomElement, onClickOutside: (_: PointerEvent) => void, isActive?: boolean): void;
100
+ /** Sends an exception object to a throwable error.
101
+ * Maps JSON Rpc errors to throwable errors.
102
+ */
103
+ export declare function mapRpcError(err: unknown): Error;
104
+ /** Catch handler for RPC methods that just returns undefined if the method is not found.
105
+ * This is useful for compatibility with versions of Lean that do not yet have the given RPC method.
106
+ */
107
+ export declare function discardMethodNotFound(e: unknown): undefined;
108
+ export type AsyncState<T> = {
109
+ state: 'loading';
110
+ } | {
111
+ state: 'resolved';
112
+ value: T;
113
+ } | {
114
+ state: 'rejected';
115
+ error: any;
116
+ };
117
+ export type AsyncWithTriggerState<T> = {
118
+ state: 'notStarted';
119
+ } | AsyncState<T>;
120
+ export declare function useAsyncWithTrigger<T>(fn: () => Promise<T>, deps?: React.DependencyList): [AsyncWithTriggerState<T>, () => Promise<void>];
121
+ /** This React hook will run the given promise function `fn` whenever the deps change
122
+ * and use it to update the status and result when the promise resolves.
123
+ *
124
+ * This function prevents race conditions if the requests resolve in a
125
+ * different order to that which they were requested in:
126
+ *
127
+ * - Request 1 is sent with, say, line=42.
128
+ * - Request 2 is sent with line=90.
129
+ * - Request 2 returns with diags=[].
130
+ * - Request 1 returns with diags=['error'].
131
+ *
132
+ * Without `useAsync` we would now return the diagnostics for line 42 even though we're at line 90.
133
+ *
134
+ * When the deps change, the function immediately returns `{ state: 'loading' }`.
135
+ */
136
+ export declare function useAsync<T>(fn: () => Promise<T>, deps?: React.DependencyList): AsyncState<T>;
137
+ /** Like {@link useAsync} but never transitions from `resolved` to `loading` by internally storing
138
+ * the latest `resolved` state and continuing to return it while an update is in flight. The lower
139
+ * amount of re-renders tends to be less visually jarring.
140
+ */
141
+ export declare function useAsyncPersistent<T>(fn: () => Promise<T>, deps?: React.DependencyList): AsyncState<T>;
@@ -0,0 +1,336 @@
1
+ /* eslint-disable @typescript-eslint/no-namespace */
2
+ /* eslint-disable react-hooks/exhaustive-deps */
3
+ import * as React from 'react';
4
+ import { isRpcError, RpcErrorCode } from '@leanprover/infoview-api';
5
+ import { EditorContext } from './contexts';
6
+ export var DocumentPosition;
7
+ (function (DocumentPosition) {
8
+ function isEqual(p1, p2) {
9
+ return p1.uri === p2.uri && p1.line === p2.line && p1.character === p2.character;
10
+ }
11
+ DocumentPosition.isEqual = isEqual;
12
+ function toTdpp(p) {
13
+ return { textDocument: { uri: p.uri }, position: { line: p.line, character: p.character } };
14
+ }
15
+ DocumentPosition.toTdpp = toTdpp;
16
+ function toString(p) {
17
+ return `${p.uri}:${p.line + 1}:${p.character}`;
18
+ }
19
+ DocumentPosition.toString = toString;
20
+ })(DocumentPosition || (DocumentPosition = {}));
21
+ export var PositionHelpers;
22
+ (function (PositionHelpers) {
23
+ function isLessThanOrEqual(p1, p2) {
24
+ return p1.line < p2.line || (p1.line === p2.line && p1.character <= p2.character);
25
+ }
26
+ PositionHelpers.isLessThanOrEqual = isLessThanOrEqual;
27
+ })(PositionHelpers || (PositionHelpers = {}));
28
+ export var RangeHelpers;
29
+ (function (RangeHelpers) {
30
+ function contains(range, pos, ignoreCharacter) {
31
+ if (!ignoreCharacter) {
32
+ if (pos.line === range.start.line && pos.character < range.start.character)
33
+ return false;
34
+ if (pos.line === range.end.line && pos.character > range.end.character)
35
+ return false;
36
+ }
37
+ return range.start.line <= pos.line && pos.line <= range.end.line;
38
+ }
39
+ RangeHelpers.contains = contains;
40
+ })(RangeHelpers || (RangeHelpers = {}));
41
+ // https://stackoverflow.com/questions/6234773/can-i-escape-html-special-chars-in-javascript
42
+ export function escapeHtml(s) {
43
+ return s
44
+ .replace(/&/g, '&amp;')
45
+ .replace(/</g, '&lt;')
46
+ .replace(/>/g, '&gt;')
47
+ .replace(/"/g, '&quot;')
48
+ .replace(/'/g, '&#039;');
49
+ }
50
+ /** @deprecated (unused) */
51
+ export function colorizeMessage(goal) {
52
+ return goal
53
+ .replace(/^([|⊢]) /gm, '<strong class="goal-vdash">$1</strong> ')
54
+ .replace(/^(\d+ goals|1 goal)/gm, '<strong class="goal-goals">$1</strong>')
55
+ .replace(/^(context|state):/gm, '<strong class="goal-goals">$1</strong>:')
56
+ .replace(/^(case) /gm, '<strong class="goal-case">$1</strong> ')
57
+ .replace(/^([^:\n< ][^:\n⊢{[(⦃]*) :/gm, '<strong class="goal-hyp">$1</strong> :');
58
+ }
59
+ export function basename(path) {
60
+ const bn = path.split(/[\\/]/).pop();
61
+ if (bn)
62
+ return bn;
63
+ else
64
+ return '';
65
+ }
66
+ /**
67
+ * A specialization of {@link React.useEffect} which executes `f` with the event data
68
+ * whenever `ev` fires.
69
+ * If `key` is provided, `f` is only invoked on events fired with that key.
70
+ */
71
+ export function useEvent(ev, f, dependencies, key) {
72
+ React.useEffect(() => {
73
+ const h = ev.on(f, key);
74
+ return () => h.dispose();
75
+ }, dependencies);
76
+ }
77
+ export function useEventResult(ev, f) {
78
+ const fn = f ?? (x => x);
79
+ const [s, setS] = React.useState(ev.current ? fn(ev.current) : undefined);
80
+ useEvent(ev, newV => setS(newV ? fn(newV) : undefined));
81
+ return s;
82
+ }
83
+ export function useServerNotificationEffect(method, f, deps) {
84
+ const ec = React.useContext(EditorContext);
85
+ React.useEffect(() => {
86
+ void ec.api.subscribeServerNotifications(method).catch(ex => {
87
+ console.error(`Failed subscribing to server notification '${method}': ${ex}`);
88
+ });
89
+ const h = ec.events.gotServerNotification.on(([thisMethod, params]) => {
90
+ if (thisMethod !== method)
91
+ return;
92
+ f(params);
93
+ });
94
+ return () => {
95
+ h.dispose();
96
+ void ec.api.unsubscribeServerNotifications(method);
97
+ };
98
+ }, deps);
99
+ }
100
+ /**
101
+ * Returns the same tuple as `setState` such that whenever a server notification with `method`
102
+ * arrives at the editor, the state will be updated according to `f`.
103
+ */
104
+ export function useServerNotificationState(method, initial, f, deps) {
105
+ const [s, setS] = React.useState(initial);
106
+ useServerNotificationEffect(method, (params) => void f(params).then(g => setS(g)), deps);
107
+ return [s, setS];
108
+ }
109
+ export function useClientNotificationEffect(method, f, deps) {
110
+ const ec = React.useContext(EditorContext);
111
+ React.useEffect(() => {
112
+ void ec.api.subscribeClientNotifications(method).catch(ex => {
113
+ console.error(`Failed subscribing to client notification '${method}': ${ex}`);
114
+ });
115
+ const h = ec.events.sentClientNotification.on(([thisMethod, params]) => {
116
+ if (thisMethod !== method)
117
+ return;
118
+ f(params);
119
+ });
120
+ return () => {
121
+ h.dispose();
122
+ void ec.api.unsubscribeClientNotifications(method);
123
+ };
124
+ }, deps);
125
+ }
126
+ /**
127
+ * Like {@link useServerNotificationState} but for client->server notifications sent by the editor.
128
+ */
129
+ export function useClientNotificationState(method, initial, f, deps) {
130
+ const [s, setS] = React.useState(initial);
131
+ useClientNotificationEffect(method, (params) => {
132
+ setS(state => f(state, params));
133
+ }, deps);
134
+ return [s, setS];
135
+ }
136
+ /**
137
+ * Returns `[{ isPaused, setPaused }, tPausable, tRef]` s.t.
138
+ * - `[isPaused, setPaused]` are the paused status state
139
+ * - for as long as `isPaused` is set, `tPausable` holds its initial value (the `t` passed before pausing)
140
+ * rather than updates with changes to `t`.
141
+ * - `tRef` can be used to overwrite the paused state
142
+ *
143
+ * To pause child components, `startPaused` can be passed in their props.
144
+ */
145
+ export function usePausableState(startPaused, t) {
146
+ const [isPaused, setPaused] = React.useState(startPaused);
147
+ const old = React.useRef(t);
148
+ if (!isPaused)
149
+ old.current = t;
150
+ return [{ isPaused, setPaused }, old.current, old];
151
+ }
152
+ /**
153
+ * Adds a unique `key` property to each element in `elems` using
154
+ * the values of (possibly non-injective) `getId`.
155
+ */
156
+ export function addUniqueKeys(elems, getId) {
157
+ const keys = {};
158
+ return elems.map(el => {
159
+ const id = getId(el);
160
+ keys[id] = (keys[id] || 0) + 1;
161
+ return { key: `${id}:${keys[id]}`, ...el };
162
+ });
163
+ }
164
+ export const LogicalDomContext = React.createContext({ registerDescendant: () => () => { } });
165
+ /** Suppose a component B appears as a React descendant of the component A. For layout reasons,
166
+ * we sometimes don't want B to appear as a descendant of A in the DOM, so we use `createPortal`.
167
+ * We may still however want to carry out `contains` checks as if B were there, i.e. according to
168
+ * the React tree structure rather than the DOM structure. While React already correctly propagates
169
+ * DOM events up the React tree, other functionality such as `contains` is not provided. We provide
170
+ * it in this hook.
171
+ *
172
+ * Accepts a ref to the observed {@link HTMLElement} (A in the example). Returns:
173
+ * - a {@link LogicalDomElement} which provides `contains` checks for that {@link HTMLElement}; and
174
+ * - a {@link LogicalDomStorage} which MUST be passed to a {@link LogicalDomContext} enclosing
175
+ * the observed {@link HTMLElement}.
176
+ *
177
+ * Additionally, any component which introduces a portal MUST call `registerDescendant` in the
178
+ * {@link LogicalDomContext} with a ref to the portalled component (B in the example). */
179
+ export function useLogicalDomObserver(elt) {
180
+ const parentCtx = React.useContext(LogicalDomContext);
181
+ const descendants = React.useRef(new Set());
182
+ // Provides a `contains` check for the children only, but not the observed element.
183
+ // We pass this to the parent context under the assumption that its own DOM-based
184
+ // `contains` check already includes the observed element.
185
+ const logicalChildrenElt = React.useMemo(() => ({
186
+ contains: (e) => {
187
+ for (const d of descendants.current) {
188
+ if (d.contains(e))
189
+ return true;
190
+ }
191
+ return false;
192
+ },
193
+ }), []);
194
+ React.useEffect(() => parentCtx.registerDescendant(logicalChildrenElt), [parentCtx, logicalChildrenElt]);
195
+ const logicalElt = React.useMemo(() => ({
196
+ contains: (e) => {
197
+ if (!elt.current)
198
+ return false;
199
+ if (elt.current.contains(e))
200
+ return true;
201
+ return logicalChildrenElt.contains(e);
202
+ },
203
+ }), [elt, logicalChildrenElt]);
204
+ const registerDescendant = React.useCallback((el) => {
205
+ descendants.current.add(el);
206
+ return () => {
207
+ descendants.current.delete(el);
208
+ };
209
+ }, []);
210
+ return [logicalElt, React.useMemo(() => ({ registerDescendant }), [registerDescendant])];
211
+ }
212
+ /**
213
+ * An effect which calls `onClickOutside` whenever an element not logically descending from `ld`
214
+ * (see {@link useLogicalDomObserver}) is clicked. Note that `onClickOutside` is not called on clicks
215
+ * on the scrollbar since these should usually not impact the app's state.
216
+ * `isActive` controls whether the `onClickOutside` handler is active. This can be useful for performance,
217
+ * since having lots of `onClickOutside` handlers when they are not needed can be expensive.
218
+ */
219
+ export function useOnClickOutside(ld, onClickOutside, isActive = true) {
220
+ React.useEffect(() => {
221
+ if (!isActive) {
222
+ return;
223
+ }
224
+ const onClickAnywhere = (e) => {
225
+ if (e.target instanceof Node && !ld.contains(e.target)) {
226
+ if (e.target instanceof Element && e.target.tagName === 'HTML') {
227
+ // then user might be clicking in a scrollbar, otherwise
228
+ // e.target would be a tag other than 'HTML'
229
+ }
230
+ else
231
+ onClickOutside(e);
232
+ }
233
+ };
234
+ document.addEventListener('pointerdown', onClickAnywhere);
235
+ return () => document.removeEventListener('pointerdown', onClickAnywhere);
236
+ }, [ld, onClickOutside, isActive]);
237
+ }
238
+ /** Sends an exception object to a throwable error.
239
+ * Maps JSON Rpc errors to throwable errors.
240
+ */
241
+ export function mapRpcError(err) {
242
+ if (isRpcError(err)) {
243
+ return new Error(`Rpc error: ${RpcErrorCode[err.code]}: ${err.message}`);
244
+ }
245
+ else if (!(err instanceof Error)) {
246
+ return new Error(`Unrecognised error ${JSON.stringify(err)}`);
247
+ }
248
+ else {
249
+ return err;
250
+ }
251
+ }
252
+ /** Catch handler for RPC methods that just returns undefined if the method is not found.
253
+ * This is useful for compatibility with versions of Lean that do not yet have the given RPC method.
254
+ */
255
+ export function discardMethodNotFound(e) {
256
+ if (isRpcError(e) && e.code === RpcErrorCode.MethodNotFound) {
257
+ return undefined;
258
+ }
259
+ else {
260
+ throw mapRpcError(e);
261
+ }
262
+ }
263
+ export function useAsyncWithTrigger(fn, deps = []) {
264
+ const asyncState = React.useRef({ state: 'notStarted' });
265
+ const asyncStateDeps = React.useRef([]);
266
+ // A monotonically increasing counter.
267
+ const tick = React.useRef(0);
268
+ // This is bumped up to the current `tick` whenever `asyncState.current` is assigned,
269
+ // in order to trigger a React update.
270
+ const [_, setUpdate] = React.useState(0);
271
+ const trigger = React.useCallback(async () => {
272
+ if (asyncState.current.state === 'loading' || asyncState.current.state === 'resolved')
273
+ return;
274
+ tick.current += 1;
275
+ asyncState.current = { state: 'loading' };
276
+ setUpdate(tick.current);
277
+ tick.current += 1;
278
+ const startTick = tick.current;
279
+ const set = (state) => {
280
+ if (tick.current === startTick) {
281
+ asyncState.current = state;
282
+ setUpdate(tick.current);
283
+ }
284
+ };
285
+ return fn().then(value => set({ state: 'resolved', value }), error => set({ state: 'rejected', error }));
286
+ }, deps);
287
+ const depsTheSame = asyncStateDeps.current.length === deps.length && asyncStateDeps.current.every((d, i) => Object.is(d, deps[i]));
288
+ if (!depsTheSame) {
289
+ tick.current += 1;
290
+ asyncState.current = { state: 'notStarted' };
291
+ asyncStateDeps.current = deps;
292
+ setUpdate(tick.current);
293
+ }
294
+ return [asyncState.current, trigger];
295
+ }
296
+ /** This React hook will run the given promise function `fn` whenever the deps change
297
+ * and use it to update the status and result when the promise resolves.
298
+ *
299
+ * This function prevents race conditions if the requests resolve in a
300
+ * different order to that which they were requested in:
301
+ *
302
+ * - Request 1 is sent with, say, line=42.
303
+ * - Request 2 is sent with line=90.
304
+ * - Request 2 returns with diags=[].
305
+ * - Request 1 returns with diags=['error'].
306
+ *
307
+ * Without `useAsync` we would now return the diagnostics for line 42 even though we're at line 90.
308
+ *
309
+ * When the deps change, the function immediately returns `{ state: 'loading' }`.
310
+ */
311
+ export function useAsync(fn, deps = []) {
312
+ const [state, trigger] = useAsyncWithTrigger(fn, deps);
313
+ if (state.state === 'notStarted') {
314
+ void trigger();
315
+ return { state: 'loading' };
316
+ }
317
+ else {
318
+ return state;
319
+ }
320
+ }
321
+ /** Like {@link useAsync} but never transitions from `resolved` to `loading` by internally storing
322
+ * the latest `resolved` state and continuing to return it while an update is in flight. The lower
323
+ * amount of re-renders tends to be less visually jarring.
324
+ */
325
+ export function useAsyncPersistent(fn, deps = []) {
326
+ const [latestState, setLatestState] = React.useState(undefined);
327
+ const state = useAsync(async () => {
328
+ const newState = await fn();
329
+ setLatestState(newState);
330
+ return newState;
331
+ }, deps);
332
+ if (state.state === 'loading' && latestState !== undefined) {
333
+ return { state: 'resolved', value: latestState };
334
+ }
335
+ return state;
336
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lean4monaco",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Monaco Editor support for the Lean 4 theorem prover.",
5
5
  "keywords": [
6
6
  "lean",