├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── README.md
├── combined_lib
├── .gitignore
├── leanpkg.bak2
└── leanpkg.toml
├── deploy.sh
├── detect_errors.py
├── elan_setup.sh
├── fetch_lean_js.sh
├── mk_library.py
├── package-lock.json
├── package.json
├── public
├── display-goal-light.svg
├── display-list-light.svg
├── index.css
├── index.html
└── lean_logo.svg
├── src
├── index.tsx
├── langservice.ts
├── syntax.ts
├── translations.json
└── typings.d.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build and deploy site and lib*.zip
2 |
3 | on:
4 | schedule:
5 | - cron: '0 3 * * *' # every day
6 | push:
7 | branches-ignore:
8 | - 'gh-pages'
9 | pull_request:
10 | branches-ignore:
11 | - 'gh-pages'
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | jobs:
16 | build:
17 | name: build and deploy site
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repo
21 | uses: actions/checkout@v2
22 |
23 | - name: install elan
24 | run: |
25 | set -o pipefail
26 | curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- --default-toolchain none -y
27 | echo "$HOME/.elan/bin" >> $GITHUB_PATH
28 |
29 | - name: Get Lean version
30 | run: |
31 | set -o pipefail
32 | source elan_setup.sh
33 | echo "LATEST_BROWSER_LEAN=$LATEST_BROWSER_LEAN" >> $GITHUB_ENV
34 |
35 | - name: install Python
36 | uses: actions/setup-python@v1
37 | with:
38 | python-version: 3.8
39 |
40 | - name: install leanproject
41 | run: |
42 | python -m pip install --upgrade pip mathlibtools
43 |
44 | - name: generate libcore.zip
45 | run: |
46 | elan override set leanprover-community/lean:$LATEST_BROWSER_LEAN
47 | ./mk_library.py -c -o dist/libcore.zip | python detect_errors.py
48 |
49 | - name: upload libcore.zip artifact
50 | uses: actions/upload-artifact@v2
51 | with:
52 | name: libcore
53 | path: ./dist/libcore.zip
54 |
55 | - name: upload libcore_meta.zip artifact
56 | uses: actions/upload-artifact@v2
57 | with:
58 | name: libcore_meta
59 | path: ./dist/libcore.*.json
60 |
61 | - name: generate library.zip
62 | run: |
63 | rm -rf .git/
64 | cd combined_lib/
65 | git init
66 | elan override set leanprover-community/lean:$LATEST_BROWSER_LEAN
67 | leanproject --no-lean-upgrade up
68 | leanproject get-mathlib-cache
69 | rm -rf _target/deps/mathlib/test
70 | rm -rf _target/deps/mathlib/scripts
71 | rm -rf _target/deps/mathlib/roadmap
72 | cd ..
73 | ./mk_library.py -i combined_lib | python detect_errors.py
74 |
75 | - name: upload library.zip artifact
76 | uses: actions/upload-artifact@v2
77 | with:
78 | name: library
79 | path: ./dist/library.zip
80 |
81 | - name: upload library_meta.zip artifact
82 | uses: actions/upload-artifact@v2
83 | with:
84 | name: library_meta
85 | path: ./dist/library.*.json
86 |
87 | - name: Checkout lean-fibonacci
88 | run: git clone https://github.com/bryangingechen/lean-fibonacci.git
89 |
90 | - name: generate libfib.zip
91 | run: |
92 | cd lean-fibonacci/
93 | elan override set leanprover-community/lean:$LATEST_BROWSER_LEAN
94 | leanpkg upgrade # don't get oleans, build for minimized libfib.zip
95 | cd ..
96 | ./mk_library.py -i lean-fibonacci -o dist/libfib.zip | python detect_errors.py
97 |
98 | - name: upload libfib.zip artifact
99 | uses: actions/upload-artifact@v2
100 | with:
101 | name: libfib
102 | path: ./dist/libfib.zip
103 |
104 | - name: upload libfib_meta.zip artifact
105 | uses: actions/upload-artifact@v2
106 | with:
107 | name: libfib_meta
108 | path: ./dist/libfib.*.json
109 |
110 | - name: build site and deploy
111 | run: |
112 | ./deploy.sh
113 |
114 | - name: Upload artifact
115 | uses: actions/upload-pages-artifact@v1
116 | with:
117 | path: 'dist'
118 |
119 | # Deploy job
120 | deploy:
121 | # Add a dependency to the build job
122 | needs: build
123 |
124 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
125 | permissions:
126 | pages: write # to deploy to Pages
127 | id-token: write # to verify the deployment originates from an appropriate source
128 |
129 | # Deploy to the github-pages environment
130 | environment:
131 | name: github-pages
132 | url: ${{ steps.deployment.outputs.page_url }}
133 |
134 | # Specify runner + deployment step
135 | runs-on: ubuntu-latest
136 | steps:
137 | - name: Deploy to GitHub Pages
138 | id: deployment
139 | uses: actions/deploy-pages@v1
140 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 | .*.sw?
4 | *-debug.log
5 | *.tgz
6 | /dist
7 | /.vscode
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lean-web-editor
2 |
3 | This repository contains the code for the leanprover-community fork of the [Lean 3 live editor](https://leanprover-community.github.io/lean-web-editor).
4 |
5 | You will need to install [nodejs](https://nodejs.org/en/) (which should include npm) to try this out locally.
6 |
7 | ## Running the development version
8 |
9 | ```
10 | npm install
11 | ./fetch_lean_js.sh
12 | ./node_modules/.bin/webpack-dev-server
13 | ```
14 |
15 | (You only need to run `npm install` and `./fetch_lean_js.sh` once after you download this repository.)
16 |
17 | The `fetch_lean_js.sh` script fetches a precompiled javascript version as well as a `library.zip` file containing the olean files for [`mathlib`](https://github.com/leanprover-community/mathlib).
18 |
19 | It is also possible to build your own Javascript / WebAssembly version of [the community fork of Lean 3](https://github.com/leanprover-community/lean). See the instructions [here](https://github.com/leanprover-community/lean/blob/master/doc/make/index.md#building-js--wasm-binaries-with-emscripten). Prebuilt versions are also included with the leanprover-community [lean releases](https://github.com/leanprover-community/lean/releases). Copy the files `lean_js_js.js`, `lean_js_wasm.js` and `lean_js_wasm.wasm` to the `dist/` directory. Note that if you choose to go this route, you will also have to recompile the `.olean` files in `library.zip` (see below).
20 |
21 | ## Deployment
22 |
23 | ```
24 | npm install
25 | ./fetch_lean_js.sh
26 | NODE_ENV=production ./node_modules/.bin/webpack
27 | ```
28 |
29 | (You only need to run `npm install` and `./fetch_lean_js.sh` if you haven't already done so above.)
30 |
31 | Then copy the `./dist` directory wherever you want.
32 |
33 | ## Creating a customized `library.zip`
34 |
35 | If you want to include custom libraries, then you need to build a suitable `library.zip` file yourself.
36 |
37 | The main tool provided by this repository is a Python script, `mk_library.py`, which requires Python 3.7 or greater. Type `./mk_library.py -h` to see all command-line options.
38 |
39 | By default, the script will run `leanpkg build` in the `/combined_lib/` subdirectory, or a Lean package that you specify with `-i`, thus generating up-to-date `.olean` files. You may see "Lean version mismatch" warnings; these should be safe to ignore. (The `-c` command-line flag skips this step if you only want bundle Lean's core library files.) The script then copies all `.olean` files that it can find in the `leanpkg.path` into a ZIP bundle (placed at `dist/library.zip` by default, can be specified with `-o`). This script will also generate a pair of `.json` files which are used by `lean-client-js-browser`:
40 | - `dist/library.info.json` contains GitHub URL prefixes to the precise commits of the Lean packages contained in `library.zip` and is used for caching,
41 | - `dist/library.olean_map.json` is used to help resolve references to individual `.lean` files returned by the Lean server to their URLs on GitHub.
42 |
43 | Here are step-by-step instructions for the most common use-cases:
44 |
45 | 1. Install Lean 3. If you plan to use the prebuilt JS/WASM versions of Lean downloaded from the [leanprover-community/lean](https://github.com/leanprover-community/lean/releases) site, i.e. if you are **not** compiling Lean with Emscripten yourself, I recommend using [`elan`](https://github.com/kha/elan) to install the latest community version using the command
46 | `elan toolchain install leanprover-community/lean:3.15.0` (replace `3.15.0` with whatever the latest released version is). If you then check `elan show`, you should see a new toolchain with the name: `leanprover-community/lean:3.15.0`. CAVEAT: if you want to bundle mathlib, make sure the version of Lean you install is compatible with the version of mathlib you want to use.
47 | If you are compiling Lean yourself, use `elan toolchain link` to set up an `elan` toolchain that points to the location of Lean on your computer. Then below, when you edit `leanpkg.toml`, ensure that `lean_version` points to this toolchain.
48 | 2. To make a ZIP bundle containing only Lean's core libraries, run `./mk_library.py -c`. You can set the output ZIP file location with `./mk_library.py -c -o /path/to/output.zip`.
49 | 3. To make a ZIP bundle containing all of the `.olean` files in one or multiple Lean packages:
50 | - Edit `combined_lib/leanpkg.toml`:
51 | - `lean_version` needs to point to the same version of Lean as the Emscripten build of Lean you plan to use. If you've installed the community version of Lean with `elan` as above, then you'll want that line to read `lean_version = "leanprover-community/lean:3.15.0"`.
52 | - Add the libraries you want to bundle to the `[dependencies]` section. You can use either `leanpkg add` or enter them manually with the format `mathlib = {git = "https://github.com/leanprover-community/mathlib", rev = "0c627fb3535d14955b2c2a24805b7cf473b4202f"}` (for dependencies retrievable using git) or `mathlib = {path = "/path/to/mathlib/"}` (for local dependencies).
53 | - In this use-case, it's important that you **don't** add a line with the `path` option to `leanpkg.toml`. Note that technically, such Lean packages are deprecated and `leanpkg` will emit a warning. Nonetheless, doing things this way causes `leanpkg build` to build all the Lean files in the packages you include, instead of just the ones depended on by the files in `/src`.
54 | - Run `./mk_library.py`. You can set the output ZIP file location with `./mk_library.py -o /path/to/output.zip`.
55 | 4. To make a ZIP bundle from a single Lean package (containing only the `.olean` files needed for the files in its `src/` directory):
56 | - To make a new Lean package, use `leanproject new` following the instructions [here](https://leanprover-community.github.io/install/project.html).
57 | - If you have an existing Lean package, you might want to make a new copy of it since otherwise you'll have to recompile when you work on it again.
58 | - Edit `lean_version` in the package's `leanpkg.toml`. It needs to point to the same version of Lean as the Emscripten build of Lean you plan to use. If you installed the latest community Lean with `elan` as above, then that line should read `lean_version = "leanprover-community/lean:3.15.0"`.
59 | - Delete `_target/` in your Lean package directory if it already exists and run `leanpkg configure` to wipe all `.olean` files in the dependencies. This step is necessary since the script in the next step will copy all `.olean` files that it can find after rebuilding.
60 | - Run `./mk_library.py -i /path/to/your_lean_package`. You can set the output ZIP file location with `./mk_library.py -i /path/to/your_lean_package -o /path/to/output.zip`.
61 |
--------------------------------------------------------------------------------
/combined_lib/.gitignore:
--------------------------------------------------------------------------------
1 | *.olean
2 | /_target
3 | /leanpkg.path
4 |
--------------------------------------------------------------------------------
/combined_lib/leanpkg.bak2:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "combined_lib"
3 | version = "3.4.2"
4 | lean_version = "leanprover-community-lean-nightly"
5 |
6 | [dependencies]
7 | mathlib = {git = "https://github.com/leanprover/mathlib", rev = "99ebbdbfe85eb37050748fc0c25c085801f3eef2"}
8 |
--------------------------------------------------------------------------------
/combined_lib/leanpkg.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "combined_lib"
3 | version = "3.46.0"
4 | lean_version = "leanprover-community/lean:3.46.0"
5 |
6 | [dependencies]
7 | mathlib = {git = "https://github.com/leanprover-community/mathlib", rev = "1e6b748c175e64dd033d7a1a1bfe3e9fe72011d3"}
8 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | set -e # fail on error
2 | set -x # echo commands
3 |
4 | LATEST_BROWSER_LEAN_URL=https://github.com/leanprover-community/lean/releases/download/v$LATEST_BROWSER_LEAN/lean-$LATEST_BROWSER_LEAN--browser.zip
5 |
6 | rm -f dist/*.worker.js
7 | npm install
8 | NODE_ENV=production ./node_modules/.bin/webpack
9 | cd dist
10 | curl -sL $LATEST_BROWSER_LEAN_URL --output leanbrowser.zip
11 | unzip -q leanbrowser.zip
12 | rm leanbrowser.zip
13 | mv build/shell/* .
14 | rm -rf build/
15 | cd ..
16 |
--------------------------------------------------------------------------------
/detect_errors.py:
--------------------------------------------------------------------------------
1 | import itertools
2 | import sys
3 |
4 | for line in sys.stdin:
5 | sys.stdout.write(line)
6 | if 'error:' in line:
7 | for line in itertools.islice(sys.stdin, 20):
8 | sys.stdout.write(line)
9 | sys.exit(1)
10 |
--------------------------------------------------------------------------------
/elan_setup.sh:
--------------------------------------------------------------------------------
1 | set -e # fail on error
2 | set -x
3 | # Get lean_version from mathlib master:
4 | export LATEST_BROWSER_LEAN=$(curl -s -N https://raw.githubusercontent.com/leanprover-community/mathlib/master/leanpkg.toml | grep -m1 lean_version | cut -d'"' -f2 | cut -d':' -f2)
5 |
6 | elan self update
7 | elan toolchain install leanprover-community/lean:$LATEST_BROWSER_LEAN
8 |
9 | set +x
10 |
--------------------------------------------------------------------------------
/fetch_lean_js.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -ex
3 |
4 | out_dir=dist
5 | mkdir -p $out_dir
6 |
7 | base_url=https://bryangingechen.github.io/lean/lean-web-editor
8 | lib_name=library # change to 'libcore' to download a bundle of the core libraries
9 |
10 | for i in lean_js_js.js lean_js_wasm.js lean_js_wasm.wasm \
11 | $lib_name.zip $lib_name.info.json $lib_name.olean_map.json
12 | do
13 | curl $base_url/$i -o $out_dir/$i
14 | done
15 |
--------------------------------------------------------------------------------
/mk_library.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import argparse
3 | import zipfile
4 | import os, subprocess, json, re
5 | from pathlib import Path
6 |
7 | parser = argparse.ArgumentParser(description='Create a library.zip olean bundle and corresponding JSON files with metadata for the lean-web-editor from a lean package.')
8 | parser.add_argument('-i', metavar='path/to/combined_lib', type=str, nargs='?',
9 | help='Lean package to bundle (default: combined_lib)', default='combined_lib')
10 | parser.add_argument('-o', metavar='path/to/library.zip', type=str, nargs='?',
11 | help='output zip file (default: dist/library.zip); metadata filenames are generated by stripping .zip from this argument and appending .info.json and .olean_map.json', default='dist/library.zip')
12 | parser.add_argument('-c', action='store_true',
13 | help='if this flag is present, only the core library will be included in the bundle')
14 |
15 | args = parser.parse_args()
16 | combined_lib = args.i
17 | combined_lib_path = str(Path(combined_lib).resolve()) + '/src'
18 | library_zip_fn = str(Path(args.o).resolve())
19 |
20 | if not args.c:
21 | os.chdir(combined_lib)
22 | subprocess.call(['leanpkg', 'build'])
23 |
24 | print('Using lean version:')
25 | lean_version = subprocess.run(['lean', '-v'], capture_output=True, encoding="utf-8").stdout
26 | print(lean_version)
27 | lean_githash = re.search("commit ([a-z0-9]{12}),", lean_version).group(1)
28 | # assume leanprover-community repo
29 | core_url = 'https://raw.githubusercontent.com/leanprover-community/lean/{0}/library/'.format(lean_githash)
30 | core_name = 'lean/library'
31 |
32 | lean_p = json.loads(subprocess.check_output(['lean', '-p']))
33 | lean_path = [Path(p).resolve() for p in lean_p["path"]]
34 |
35 | already_seen = set()
36 | lib_info = {}
37 | oleans = {}
38 | num_olean = {}
39 | Path(library_zip_fn).parent.mkdir(parents=True, exist_ok=True)
40 | with zipfile.ZipFile(library_zip_fn, mode='w', compression=zipfile.ZIP_DEFLATED, allowZip64=False, compresslevel=9) as zf:
41 | for p in lean_path:
42 | parts = p.parts
43 | if str(p.resolve()) == combined_lib_path: # if using combined_lib/src
44 | lib_name = parts[-2]
45 | lib_info[lib_name] = '/library/' + lib_name
46 | elif parts[-1] != 'library':
47 | lib_name = parts[-2] # assume lean_path contains _target/deps/name/src
48 | git_dir = str(p.parent)+'/.git'
49 | lib_rev = subprocess.run(['git', '--git-dir='+git_dir, 'rev-parse', 'HEAD'], capture_output=True, encoding="utf-8").stdout.rstrip()
50 | lib_repo_url = subprocess.run(['git', '--git-dir='+git_dir, 'config', '--get', 'remote.origin.url'], capture_output=True, encoding="utf-8").stdout.rstrip()
51 | # assume that repos are hosted at github
52 | lib_repo_match = re.search(r'github\.com[:/]([^\.]*)', lib_repo_url)
53 | if lib_repo_match:
54 | lib_repo = lib_repo_match.group(1)
55 | lib_info[lib_name] = 'https://raw.githubusercontent.com/{0}/{1}/src/'.format(lib_repo, lib_rev)
56 | elif lib_repo_url:
57 | lib_info[lib_name] = lib_repo_url
58 | else:
59 | lib_info[lib_name] = '/library/' + lib_name
60 | else:
61 | lib_name = core_name
62 | lib_info[lib_name] = core_url
63 | if lib_name not in num_olean.keys():
64 | num_olean[lib_name] = 0
65 | for fn in p.glob('**/*.olean'):
66 | rel = fn.relative_to(p)
67 | # ignore transitive dependencies
68 | if '_target' in rel.parts:
69 | continue
70 | # ignore olean files from deleted / renamed lean files
71 | if not fn.with_suffix('.lean').is_file():
72 | continue
73 | elif rel in already_seen:
74 | print('duplicate: {0}'.format(fn))
75 | else:
76 | zf.write(fn, arcname=str(rel))
77 | # uncomment if you want to use Lean 3.7.0 (fixed in 3.7.1)
78 | # zf.write(fn.with_suffix('.lean'), arcname=str(rel.with_suffix('.lean')))
79 | oleans[str(rel)[:-6]] = lib_name
80 | num_olean[lib_name] += 1
81 | already_seen.add(rel)
82 | if num_olean[lib_name] == 0:
83 | del lib_info[lib_name]
84 | else:
85 | print('Added {0} olean files from {1}'.format(num_olean[lib_name], lib_name))
86 | print('Created {0} with {1} olean files'.format(library_zip_fn, len(already_seen)))
87 |
88 | library_prefix = library_zip_fn.split('.')[0]
89 | info_fn = library_prefix + '.info.json'
90 | with open(info_fn, 'w') as f:
91 | json.dump(lib_info, f, separators=(',', ':'))
92 | f.write('\n')
93 | print('Wrote info to {0}'.format(info_fn))
94 |
95 | map_fn = library_prefix + '.olean_map.json'
96 | with open(map_fn, 'w') as f:
97 | json.dump(oleans, f, separators=(',', ':'))
98 | f.write('\n')
99 | print('Wrote olean map to {0}'.format(map_fn))
100 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lean-web-editor",
3 | "version": "1.0.0",
4 | "description": "Lean Web Editor",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "Gabriel Ebner",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "@types/node": "^9.4.6",
13 | "@types/react": "^16.14.42",
14 | "@types/react-dom": "^16.9.19",
15 | "buffer": "^6.0.3",
16 | "lean-client-js-browser": "^3.5.1",
17 | "monaco-editor": "0.10.1",
18 | "path-browserify": "^1.0.1",
19 | "react": "^16.14.0",
20 | "react-dom": "^16.14.0",
21 | "react-split-pane": "0.1.77"
22 | },
23 | "devDependencies": {
24 | "copy-webpack-plugin": "^11.0.0",
25 | "html-webpack-plugin": "^5.5.1",
26 | "ts-loader": "^9.4.3",
27 | "tslint": "^6.1.3",
28 | "typescript": "^5.0.4",
29 | "webpack": "^5.84.1",
30 | "webpack-cli": "^5.1.1",
31 | "webpack-dev-server": "^4.15.0",
32 | "worker-loader": "^3.0.8"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/public/display-goal-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/display-list-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/public/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | .code-block {
6 | display: block;
7 | font-family: monospace;
8 | white-space: pre-wrap;
9 | overflow-wrap: break-word;
10 | margin-top: 1em;
11 | font-size: 110%;
12 | }
13 |
14 | .info-header {
15 | border-bottom: 1px solid;
16 | font-weight: bold;
17 | font-family: sans-serif;
18 | }
19 |
20 | .running {
21 | display: none;
22 | }
23 |
24 | .leanlink {
25 | font-size: 80%;
26 | }
27 |
28 | .modalButton {
29 | padding: .1em .5em;
30 | color: #24a;
31 | background: #aaf;
32 | border: 2px solid #ccf;
33 | border-radius: 1em;
34 | cursor: pointer;
35 | transition: background .15s ease-out;
36 | }
37 |
38 | .modalButton:hover {
39 | background: #cdf;
40 | }
41 |
42 | .modalButton:focus {
43 | outline: 0;
44 | box-shadow: 0 0 4px currentColor;
45 | }
46 |
47 | .c-modal-cover {
48 | position: fixed;
49 | top: 0;
50 | left: 0;
51 | width: 100%;
52 | height: 100%;
53 | z-index: 10;
54 | transform: translateZ(0);
55 | background-color: rgba(0, 0, 0, 0.15);
56 | }
57 |
58 | .c-modal {
59 | position: fixed;
60 | top: 0;
61 | left: 0;
62 | height: 100%;
63 | padding: 2.5em 1.5em 1.5em 1.5em;
64 | background-color: #FFFFFF;
65 | box-shadow: 0 0 10px 3px rgba(0, 0, 0, 0.1);
66 | overflow-y: auto;
67 | -webkit-overflow-scrolling: touch;
68 | }
69 |
70 | @media screen and (min-width: 500px) {
71 | .c-modal {
72 | left: 50%;
73 | top: 50%;
74 | height: calc(100% - 5em);
75 | transform: translate(-50%, -50%);
76 | max-width: 30em;
77 | max-height: calc(100% - 2em);
78 | }
79 | }
80 |
81 | .c-modal h1 {
82 | position: absolute;
83 | top: 0;
84 | left: 0;
85 | padding: .25em .5em;
86 | margin: 0;
87 | }
88 |
89 | .c-modal__close {
90 | position: absolute;
91 | top: 0;
92 | right: 0;
93 | padding: .5em;
94 | line-height: 1;
95 | background: #f6f6f7;
96 | border: 0;
97 | box-shadow: 0;
98 | cursor: pointer;
99 | }
100 |
101 | .c-modal__close-icon {
102 | width: 25px;
103 | height: 25px;
104 | fill: transparent;
105 | stroke: black;
106 | stroke-linecap: round;
107 | stroke-width: 2;
108 | }
109 |
110 | .c-modal__body {
111 | padding-top: .25em;
112 | }
113 |
114 | .u-hide-visually {
115 | border: 0 !important;
116 | clip: rect(0 0 0 0) !important;
117 | height: 1px !important;
118 | margin: -1px !important;
119 | overflow: hidden !important;
120 | padding: 0 !important;
121 | position: absolute !important;
122 | width: 1px !important;
123 | white-space: nowrap !important;
124 | }
125 |
126 | .leaneditorContainer {
127 | height: 99vh;
128 | display: flex;
129 | flex-direction: column;
130 | }
131 |
132 | .leanheader {
133 | display: flex;
134 | }
135 |
136 | .headerContainer {
137 | overflow: hidden;
138 | }
139 |
140 | .headerForms {
141 | padding: 1em 1em 0.25em 1em;
142 | flex-grow: 1;
143 | }
144 |
145 | .urlForm {
146 | display: flex;
147 | }
148 |
149 | .urlForm form {
150 | display: flex;
151 | justify-content: flex-end;
152 | flex: 1;
153 | }
154 |
155 | .urlForm input[type='text'] {
156 | flex: 1;
157 | }
158 |
159 | .editorContainer {
160 | flex: 1;
161 | position: relative;
162 | border-top: 1.5px dotted rgb(93, 101, 116);
163 | }
164 |
165 | .monacoContainer {
166 | height: 98%;
167 | width: 100%;
168 | margin: 1ex 0em 1ex -1.5ex;
169 | padding-right: 10px;
170 | overflow: hidden;
171 | }
172 |
173 | .infoContainer {
174 | margin: 1ex;
175 | }
176 |
177 | .infoview-buttons {
178 | cursor: pointer;
179 | margin-right: 1ex;
180 | display: block;
181 | float: right;
182 | }
183 |
184 | .toggleDoc {
185 | margin-top: 1em;
186 | cursor: pointer;
187 | white-space: pre-wrap;
188 | }
189 |
190 | /* https://alligator.io/css/collapsible/ */
191 | .wrap-collabsible {
192 | margin-bottom: 1.2rem 0;
193 | }
194 |
195 | input[type='checkbox']#collapsible {
196 | display: none;
197 | }
198 |
199 | .lbl-toggle {
200 | display: block;
201 | text-align: center;
202 | color: #222;
203 | cursor: pointer;
204 | transition: all 0.25s ease-out;
205 | }
206 |
207 | .lbl-toggle:hover {
208 | color: #44a;
209 | }
210 |
211 | .lbl-toggle::before {
212 | content: ' ';
213 | display: inline-block;
214 |
215 | border-top: 5px solid transparent;
216 | border-bottom: 5px solid transparent;
217 | border-left: 5px solid currentColor;
218 | vertical-align: middle;
219 | margin-right: .7rem;
220 | transform: translateY(-2px);
221 |
222 | transition: transform .2s ease-out;
223 | }
224 |
225 | .toggle:checked + .lbl-toggle::before {
226 | transform: rotate(90deg) translateX(-3px);
227 | }
228 |
229 | .collapsible-content {
230 | max-height: 0px;
231 | overflow: hidden;
232 | transition: max-height .25s ease-in-out;
233 | }
234 |
235 | .toggle:checked + .lbl-toggle + .collapsible-content {
236 | max-height: 10em;
237 | }
238 |
239 | .toggle:checked + .lbl-toggle {
240 | border-bottom-right-radius: 0;
241 | border-bottom-left-radius: 0;
242 | }
243 |
244 | @media screen and (max-width: 800px) {
245 | .logo {
246 | display: none;
247 | }
248 | .running {
249 | display: inline;
250 | }
251 | .leanheader {
252 | font-size: 80%;
253 | }
254 | .leanlink {
255 | font-size: 110%;
256 | }
257 | }
258 |
259 | @media screen and (max-width: 450px) {
260 | .url {
261 | display: none;
262 | }
263 | }
264 |
265 | .Resizer {
266 | -moz-box-sizing: border-box;
267 | -webkit-box-sizing: border-box;
268 | box-sizing: border-box;
269 | background: #000;
270 | opacity: .2;
271 | z-index: 1;
272 | -moz-background-clip: padding;
273 | -webkit-background-clip: padding;
274 | background-clip: padding-box;
275 | }
276 |
277 | .Resizer:hover {
278 | -webkit-transition: all 2s ease;
279 | transition: all 2s ease;
280 | }
281 |
282 | .Resizer.horizontal {
283 | background-color: gray;
284 | height: 11px;
285 | margin: 5px 0;
286 | border-top: 5px solid rgba(255, 255, 255, 0);
287 | border-bottom: 5px solid rgba(255, 255, 255, 0);
288 | cursor: row-resize;
289 | width: 100%;
290 | }
291 |
292 | .Resizer.horizontal:hover {
293 | border-top: 5px solid rgba(0, 0, 0, 0.5);
294 | border-bottom: 5px solid rgba(0, 0, 0, 0.5);
295 | }
296 |
297 | .Resizer.vertical {
298 | background-color: gray;
299 | width: 11px;
300 | border-left: 5px solid rgba(255, 255, 255, 0);
301 | border-right: 5px solid rgba(255, 255, 255, 0);
302 | cursor: col-resize;
303 | }
304 |
305 | .Resizer.vertical:hover {
306 | border-left: 5px solid rgba(0, 0, 0, 0.5);
307 | border-right: 5px solid rgba(0, 0, 0, 0.5);
308 | }
309 |
310 | .Pane.horizontal.Pane2 {
311 | border-top: 1px solid gray;
312 | }
313 |
314 | .Pane.vertical.Pane2 {
315 | border-left: 1px solid gray;
316 | }
317 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Lean 3 Web Editor
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/public/lean_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
18 |
20 |
21 |
23 | image/svg+xml
24 |
26 |
27 |
28 |
29 |
49 |
51 |
53 |
57 |
62 |
63 |
67 |
72 |
73 |
77 |
82 |
83 |
87 |
92 |
93 |
97 |
102 |
103 |
107 |
112 |
113 |
117 |
122 |
123 |
127 |
132 |
133 |
137 |
142 |
143 |
147 |
152 |
153 |
157 |
162 |
163 |
167 |
172 |
173 |
177 |
182 |
183 |
187 |
192 |
193 |
197 |
202 |
203 |
207 |
212 |
213 |
217 |
222 |
223 |
227 |
232 |
233 |
234 |
237 |
244 |
245 |
246 |
250 |
253 |
260 |
261 |
264 |
271 |
272 |
275 |
282 |
283 |
289 |
295 |
298 |
305 |
312 |
319 |
326 |
333 |
340 |
347 |
354 |
361 |
368 |
375 |
376 |
379 |
386 |
393 |
400 |
401 |
402 |
403 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | ///
2 | import { InfoRecord, LeanJsOpts, Message } from 'lean-client-js-browser';
3 | import * as React from 'react';
4 | import { createPortal, findDOMNode, render } from 'react-dom';
5 | import * as sp from 'react-split-pane';
6 | import { allMessages, checkInputCompletionChange, checkInputCompletionPosition, currentlyRunning, delayMs,
7 | registerLeanLanguage, server, tabHandler } from './langservice';
8 | export const SplitPane: any = sp;
9 |
10 | function leanColorize(text: string): string {
11 | // TODO(gabriel): use promises
12 | const colorized: string = (monaco.editor.colorize(text, 'lean', {}) as any)._value;
13 | return colorized.replace(/ /g, ' ');
14 | }
15 |
16 | interface MessageWidgetProps {
17 | msg: Message;
18 | }
19 | function MessageWidget({msg}: MessageWidgetProps) {
20 | const colorOfSeverity = {
21 | information: 'green',
22 | warning: 'orange',
23 | error: 'red',
24 | };
25 | // TODO: links and decorations on hover
26 | return (
27 |
28 |
29 | {msg.pos_line}:{msg.pos_col}: {msg.severity}: {msg.caption}
30 |
31 |
32 | );
33 | }
34 |
35 | interface Position {
36 | line: number;
37 | column: number;
38 | }
39 |
40 | interface GoalWidgetProps {
41 | goal: InfoRecord;
42 | position: Position;
43 | }
44 | function GoalWidget({goal, position}: GoalWidgetProps) {
45 | const tacticHeader = goal.text &&
46 | {position.line}:{position.column}: tactic {
47 | {goal.text} }
;
48 | const docs = goal.doc && ;
49 |
50 | const typeHeader = goal.type &&
51 | {position.line}:{position.column}: type {
52 | goal['full-id'] && of
53 | {goal['full-id']} }
;
54 | const typeBody = (goal.type && !goal.text) // don't show type of tactics
55 | &&
')}}/>;
57 |
58 | const goalStateHeader = goal.state &&
59 | {position.line}:{position.column}: goal
;
60 | const goalStateBody = goal.state &&
'}} />;
62 |
63 | return (
64 | // put tactic state first so that there's less jumping around when the cursor moves
65 |
66 | {goalStateHeader}
67 | {goalStateBody}
68 | {tacticHeader || typeHeader}
69 | {typeBody}
70 | {docs}
71 |
72 | );
73 | }
74 |
75 | interface ToggleDocProps {
76 | doc: string;
77 | }
78 | interface ToggleDocState {
79 | showDoc: boolean;
80 | }
81 | class ToggleDoc extends React.Component {
82 | constructor(props: ToggleDocProps) {
83 | super(props);
84 | this.state = { showDoc: this.props.doc.length < 80 };
85 | this.onClick = this.onClick.bind(this);
86 | }
87 | onClick() {
88 | this.setState({ showDoc: !this.state.showDoc });
89 | }
90 | render() {
91 | return
92 | {this.state.showDoc ?
93 | this.props.doc : // TODO: markdown / highlighting?
94 | {this.props.doc.slice(0, 75)} [...] }
95 |
96 |
97 |
;
98 | }
99 | }
100 |
101 | enum DisplayMode {
102 | OnlyState, // only the state at the current cursor position including the tactic state
103 | AllMessage, // all messages
104 | }
105 |
106 | interface InfoViewProps {
107 | file: string;
108 | cursor?: Position;
109 | }
110 | interface InfoViewState {
111 | goal?: GoalWidgetProps;
112 | messages: Message[];
113 | displayMode: DisplayMode;
114 | }
115 | class InfoView extends React.Component {
116 | private subscriptions: monaco.IDisposable[] = [];
117 |
118 | constructor(props: InfoViewProps) {
119 | super(props);
120 | this.state = {
121 | messages: [],
122 | displayMode: DisplayMode.OnlyState,
123 | };
124 | }
125 | componentWillMount() {
126 | this.updateMessages(this.props);
127 | let timer = null; // debounce
128 | this.subscriptions.push(
129 | server.allMessages.on((allMsgs) => {
130 | if (timer) { clearTimeout(timer); }
131 | timer = setTimeout(() => {
132 | this.updateMessages(this.props);
133 | this.refreshGoal(this.props);
134 | }, 100);
135 | }),
136 | );
137 | }
138 | componentWillUnmount() {
139 | for (const s of this.subscriptions) {
140 | s.dispose();
141 | }
142 | this.subscriptions = [];
143 | }
144 | componentWillReceiveProps(nextProps) {
145 | if (nextProps.cursor === this.props.cursor) { return; }
146 | this.updateMessages(nextProps);
147 | this.refreshGoal(nextProps);
148 | }
149 |
150 | updateMessages(nextProps) {
151 | this.setState({
152 | messages: allMessages.filter((v) => v.file_name === this.props.file),
153 | });
154 | }
155 |
156 | refreshGoal(nextProps?: InfoViewProps) {
157 | if (!nextProps) {
158 | nextProps = this.props;
159 | }
160 | if (!nextProps.cursor) {
161 | return;
162 | }
163 |
164 | const position = nextProps.cursor;
165 | server.info(nextProps.file, position.line, position.column).then((res) => {
166 | this.setState({goal: res.record && { goal: res.record, position }});
167 | });
168 | }
169 |
170 | render() {
171 | const goal = (this.state.displayMode === DisplayMode.OnlyState) &&
172 | this.state.goal &&
173 | ({GoalWidget(this.state.goal)}
);
174 | const filteredMsgs = (this.state.displayMode === DisplayMode.AllMessage) ?
175 | this.state.messages :
176 | this.state.messages.filter(({pos_col, pos_line, end_pos_col, end_pos_line}) => {
177 | if (!this.props.cursor) { return false; }
178 | const {line, column} = this.props.cursor;
179 | return pos_line <= line &&
180 | ((!end_pos_line && line === pos_line) || line <= end_pos_line) &&
181 | (line !== pos_line || pos_col <= column) &&
182 | (line !== end_pos_line || end_pos_col >= column);
183 | });
184 | const msgs = filteredMsgs.map((msg, i) =>
185 | ({MessageWidget({msg})}
));
186 | return (
187 |
188 |
189 |
{
192 | this.setState({ displayMode: DisplayMode.OnlyState });
193 | }}/>
194 |
{
197 | this.setState({ displayMode: DisplayMode.AllMessage });
198 | }}/>
199 |
200 | {goal}
201 | {msgs}
202 |
203 | );
204 | }
205 | }
206 |
207 | interface PageHeaderProps {
208 | file: string;
209 | url: string;
210 | onSubmit: (value: string) => void;
211 | status: string;
212 | onSave: () => void;
213 | onLoad: (localFile: string, lastFileName: string) => void;
214 | clearUrlParam: () => void;
215 | onChecked: () => void;
216 | }
217 | interface PageHeaderState {
218 | currentlyRunning: boolean;
219 | }
220 | class PageHeader extends React.Component {
221 | private subscriptions: monaco.IDisposable[] = [];
222 |
223 | constructor(props: PageHeaderProps) {
224 | super(props);
225 | this.state = { currentlyRunning: true };
226 | this.onFile = this.onFile.bind(this);
227 | // this.restart = this.restart.bind(this);
228 | }
229 |
230 | componentWillMount() {
231 | this.updateRunning(this.props);
232 | this.subscriptions.push(
233 | currentlyRunning.updated.on((fns) => this.updateRunning(this.props)),
234 | );
235 | }
236 | componentWillUnmount() {
237 | for (const s of this.subscriptions) {
238 | s.dispose();
239 | }
240 | this.subscriptions = [];
241 | }
242 | componentWillReceiveProps(nextProps) {
243 | this.updateRunning(nextProps);
244 | }
245 |
246 | updateRunning(nextProps) {
247 | this.setState({
248 | currentlyRunning: currentlyRunning.value.indexOf(nextProps.file) !== -1,
249 | });
250 | }
251 |
252 | onFile(e) {
253 | const reader = new FileReader();
254 | const file = e.target.files[0];
255 | reader.readAsText(file);
256 | reader.onload = () => this.props.onLoad(reader.result as string, file.name);
257 | this.props.clearUrlParam();
258 | }
259 |
260 | // This doesn't work! /test.lean not found after restarting
261 | // restart() {
262 | // // server.restart();
263 | // registerLeanLanguage(leanJsOpts);
264 | // }
265 |
266 | render() {
267 | const isRunning = this.state.currentlyRunning ? 'busy...' : 'ready!';
268 | const runColor = this.state.currentlyRunning ? 'orange' : 'lightgreen';
269 | // TODO: add input for delayMs
270 | // checkbox for console spam
271 | // server.logMessagesToConsole = true;
272 | return (
273 |
274 |
276 |
277 | Lean is {isRunning}
278 |
279 |
280 |
282 |
310 |
311 |
312 | );
313 | }
314 | }
315 |
316 | interface UrlFormProps {
317 | url: string;
318 | onSubmit: (value: string) => void;
319 | clearUrlParam: () => void;
320 | }
321 | interface UrlFormState {
322 | value: string;
323 | }
324 | class UrlForm extends React.Component {
325 | constructor(props) {
326 | super(props);
327 | this.state = {value: this.props.url};
328 |
329 | this.handleChange = this.handleChange.bind(this);
330 | this.handleSubmit = this.handleSubmit.bind(this);
331 | }
332 |
333 | handleChange(event) {
334 | this.setState({value: event.target.value});
335 | this.props.clearUrlParam();
336 | }
337 |
338 | handleSubmit(event) {
339 | this.props.onSubmit(this.state.value);
340 | event.preventDefault();
341 | }
342 |
343 | render() {
344 | return (
345 |
346 |
351 | );
352 | }
353 | }
354 |
355 | interface ModalState {
356 | isOpen: boolean;
357 | }
358 | // https://assortment.io/posts/accessible-modal-component-react-portals-part-1 & 2
359 | // TODO: change focus back to button when modal closes
360 | class Modal extends React.Component<{}, ModalState> {
361 | private modalNode: Node;
362 | constructor(props) {
363 | super(props);
364 | this.state = {
365 | isOpen: false,
366 | };
367 | this.open = this.open.bind(this);
368 | this.close = this.close.bind(this);
369 | this.keyDown = this.keyDown.bind(this);
370 | this.clickAway = this.clickAway.bind(this);
371 | }
372 |
373 | open() {
374 | this.setState({ isOpen: true }, () => {
375 | });
376 | }
377 | close() {
378 | this.setState({ isOpen: false });
379 | }
380 | keyDown({ keyCode }) {
381 | return keyCode === 27 && this.close();
382 | }
383 | clickAway(e) {
384 | if (this.modalNode && this.modalNode.contains(e.target)) { return; }
385 | this.close();
386 | }
387 |
388 | render() {
389 | return (
390 |
391 | ?
392 | {this.state.isOpen &&
393 | this.modalNode = n}/>}
395 |
396 | );
397 | }
398 | }
399 |
400 | function ModalContent({ onClose, modalRef, onKeyDown, clickAway }) {
401 | const libinfo = []; // populated with info about included libraries
402 | if (info) {
403 | for (const k in info) {
404 | if (info.hasOwnProperty(k)) {
405 | const v = info[k];
406 | if (v.match(/^https:\/\/raw\.githubusercontent\.com/)) {
407 | const urlArray = v.slice(34).split('/').slice(0, 3);
408 | const commit = urlArray[2].slice(0, 8);
409 | urlArray.unshift('https://github.com');
410 | urlArray.splice(3, 0, 'tree');
411 | const url = urlArray.join('/');
412 | libinfo.push();
416 | } else {
417 | libinfo.push(
419 | {k} : {v}
420 |
);
421 | }
422 | }
423 | }
424 | }
425 |
426 | return createPortal(
427 |
428 |
429 |
Lean web editor:
430 |
431 | Close
432 |
433 |
434 |
435 |
436 |
This page runs a WebAssembly or JavaScript version of Lean
437 | 3 , a theorem prover and programming language developed
438 | at Microsoft Research .
439 |
440 |
New to Lean?
441 |
Please note that this editor is not really meant for serious use.
442 | Most Lean users use the Lean VS Code or Emacs extensions to write proofs and programs.
443 | There are good installation guides for Lean 3 and its standard library "mathlib"
444 | here .
445 | The books Theorem Proving in Lean
446 | and Logic and Proof are reasonable places
447 | to start learning Lean. For a more interactive approach,
448 | you might try the
449 | "Natural number game" . For more resources, see the
450 | Learning Lean page .
451 | If you have questions, drop by the
452 | leanprover zulip chat .
453 |
454 |
Using this editor:
455 |
Type Lean code into the editor panel or load and edit a .lean file from the web or your computer
456 | using the input forms in the header.
457 | If there are errors, warnings, or info messages, they will be underlined in red or green in the editor
458 | and a message will be displayed in the info panel.
459 |
You can input unicode characters by entering "\" and then typing the corresponding code (see below)
460 | and then either typing a space or a comma or hitting TAB.
461 |
Here are a few common codes. Note that many other LaTeX commands will work as well:
462 | "lam" for "λ", "to" (or "->") for "→", "l" (or "<-") for "←", "u" for "↑", "d" for "↓",
463 | "in" for "∈", "and" for "∧", "or" for "∨", "x" for "×",
464 | "le" and "ge" (or "<=" and ">=") for "≤" and "≥",
465 | "<" and ">" for "⟨" and "⟩",
466 | "ne" for "≠", "nat" for "ℕ", "not" for "¬", "int" for "ℤ",
467 | (For full details,
468 | see this
469 | list .)
470 |
To see the type of a term, hover over it to see a popup, or place your cursor in the text to
471 | view the type and / or docstring in the info panel
472 | (on the right, or below, depending on your browser's aspect ratio).
473 |
Click the colored bar to show / hide the header UI.
474 |
Drag the separating line between the editor panel and info panels to adjust their relative sizes.
475 |
476 |
About this editor:
477 |
This editor is a fork of the
478 | original lean-web-editor app
479 | (written in TypeScript+React and using the Monaco
480 | editor; see the original GitHub repository here ).
481 | This page also uses a forked
482 | version of the lean-client-browser package
483 | that caches the library.zip
file
484 | in IndexedDB .
485 |
Lean packages in library.zip:
486 | {libinfo}
487 |
Settings:
488 |
{
490 | if (!e.target.checked && !document.getElementById('hideUnderline')) {
491 | const style = document.createElement('style');
492 | style.type = 'text/css';
493 | style.id = 'hideUnderline';
494 | style.appendChild(document.createTextNode(`.monaco-editor .greensquiggly,
495 | .monaco-editor .redsquiggly { background-size:0px; }`));
496 | document.head.appendChild(style);
497 | window.localStorage.setItem('underline', 'true');
498 | } else if (document.getElementById('hideUnderline')) {
499 | document.getElementById('hideUnderline').remove();
500 | window.localStorage.setItem('underline', 'false');
501 | }
502 | }}/>
503 | Decorate code with squiggly underlines for errors / warnings / info
504 |
{
506 | if (!e.target.checked && !document.getElementById('hideDocs')) {
507 | const style = document.createElement('style');
508 | style.type = 'text/css';
509 | style.id = 'hideDocs';
510 | style.appendChild(document.createTextNode(`.toggleDoc, .doc-header { display:none; }`));
511 | document.head.appendChild(style);
512 | window.localStorage.setItem('docs', 'true');
513 | } else if (document.getElementById('hideDocs')) {
514 | document.getElementById('hideDocs').remove();
515 | window.localStorage.setItem('dosc', 'false');
516 | }
517 | }}/>
518 | Show tactic docs in info panel (regardless of whether this is checked,
519 | tactic docs can be viewed by hovering your cursor over the tactic name)
520 |
Debug:
521 |
{
522 | server.logMessagesToConsole = e.target.checked;
523 | window.localStorage.setItem('logging', e.target.checked ? 'true' : 'false');
524 | console.log(`server logging ${server.logMessagesToConsole ?
525 | 'start' : 'end'}ed!`);
526 | }}/>
527 | Log server messages to console
528 |
{
529 | const req = indexedDB.deleteDatabase('leanlibrary');
530 | req.onsuccess = () => {
531 | console.log('Deleted leanlibrary successfully');
532 | (location.reload as (cache: boolean) => void)(true);
533 | };
534 | req.onerror = () => {
535 | console.log("Couldn't delete leanlibrary");
536 | };
537 | req.onblocked = () => {
538 | console.log("Couldn't delete leanlibrary due to the operation being blocked");
539 | };
540 | }}>Clear library cache and refresh
541 |
{
542 | if ((self as any).WebAssembly) {
543 | fetch(leanJsOpts.webassemblyJs, {cache: 'reload'})
544 | .then(() => fetch(leanJsOpts.webassemblyWasm, {cache: 'reload'}))
545 | .then(() => {
546 | console.log('Updated JS & WASM cache successfully');
547 | (location.reload as (cache: boolean) => void)(true);
548 | }).catch((e) => console.log(e));
549 | } else {
550 | fetch(leanJsOpts.javascript, {cache: 'reload'})
551 | .then(() => {
552 | console.log('Updated JS cache successfully');
553 | (location.reload as (cache: boolean) => void)(true);
554 | }).catch((e) => console.log(e));
555 | }
556 | }}>Clear JS/WASM cache and refresh
557 |
558 |
559 | ,
560 | document.body);
561 | }
562 |
563 | interface LeanEditorProps {
564 | file: string;
565 | initialValue: string;
566 | onValueChange?: (value: string) => void;
567 | initialUrl: string;
568 | onUrlChange?: (value: string) => void;
569 | clearUrlParam: () => void;
570 | }
571 | interface LeanEditorState {
572 | cursor?: Position;
573 | split: 'vertical' | 'horizontal';
574 | url: string;
575 | status: string;
576 | size: number;
577 | checked: boolean;
578 | lastFileName: string;
579 | }
580 | class LeanEditor extends React.Component {
581 | model: monaco.editor.IModel;
582 | editor: monaco.editor.IStandaloneCodeEditor;
583 | constructor(props: LeanEditorProps) {
584 | super(props);
585 | this.state = {
586 | split: 'vertical',
587 | url: this.props.initialUrl,
588 | status: null,
589 | size: null,
590 | checked: true,
591 | lastFileName: this.props.file,
592 | };
593 | this.model = monaco.editor.createModel(this.props.initialValue, 'lean', monaco.Uri.file(this.props.file));
594 | this.model.updateOptions({ tabSize: 2 });
595 | this.model.onDidChangeContent((e) => {
596 | checkInputCompletionChange(e, this.editor, this.model);
597 | const val = this.model.getValue();
598 |
599 | // do not change code URL param unless user has actually typed
600 | // (this makes the #url=... param a little more "sticky")
601 | return (!e.isFlush || !val) && this.props.onValueChange &&
602 | this.props.onValueChange(val);
603 | });
604 |
605 | this.updateDimensions = this.updateDimensions.bind(this);
606 | this.dragFinished = this.dragFinished.bind(this);
607 | this.onSubmit = this.onSubmit.bind(this);
608 | this.onSave = this.onSave.bind(this);
609 | this.onLoad = this.onLoad.bind(this);
610 | this.onChecked = this.onChecked.bind(this);
611 | }
612 | componentDidMount() {
613 | /* TODO: factor this out */
614 | const ta = document.createElement('div');
615 | ta.style.fontSize = '1px';
616 | ta.style.lineHeight = '1';
617 | ta.innerHTML = 'a';
618 | document.body.appendChild(ta);
619 | const minimumFontSize = ta.clientHeight;
620 | ta.remove();
621 | const node = findDOMNode(this.refs.monaco) as HTMLElement;
622 | const DEFAULT_FONT_SIZE = 12;
623 | const options: monaco.editor.IEditorConstructionOptions = {
624 | selectOnLineNumbers: true,
625 | roundedSelection: false,
626 | readOnly: false,
627 | theme: 'vs',
628 | cursorStyle: 'line',
629 | automaticLayout: true,
630 | cursorBlinking: 'solid',
631 | model: this.model,
632 | minimap: {enabled: false},
633 | wordWrap: 'on',
634 | scrollBeyondLastLine: false,
635 | fontSize: Math.max(DEFAULT_FONT_SIZE, minimumFontSize),
636 | };
637 | this.editor = monaco.editor.create(node, options);
638 |
639 | // context key which keeps track of whether unicode translation is possible
640 | const canTranslate = this.editor.createContextKey('canTranslate', false);
641 | this.editor.addCommand(monaco.KeyCode.Tab, () => {
642 | tabHandler(this.editor, this.model);
643 | }, 'canTranslate');
644 | this.editor.onDidChangeCursorPosition((e) => {
645 | canTranslate.set(checkInputCompletionPosition(e, this.editor, this.model));
646 | this.setState({cursor: {line: e.position.lineNumber, column: e.position.column - 1}});
647 | });
648 |
649 | this.determineSplit();
650 | window.addEventListener('resize', this.updateDimensions);
651 | }
652 | componentWillUnmount() {
653 | this.editor.dispose();
654 | this.editor = undefined;
655 | window.removeEventListener('resize', this.updateDimensions);
656 | }
657 | componentDidUpdate() {
658 | // if state url is not null, fetch, then set state url to null again
659 | if (this.state.url) {
660 | fetch(this.state.url).then((s) => s.text())
661 | .then((s) => {
662 | this.model.setValue(s);
663 | this.setState({ status: null });
664 | })
665 | .catch((e) => this.setState({ status: e.toString() }));
666 | this.setState({ url: null });
667 | }
668 | }
669 |
670 | updateDimensions() {
671 | this.determineSplit();
672 | }
673 | determineSplit() {
674 | const node = findDOMNode(this.refs.root) as HTMLElement;
675 | this.setState({split: node.clientHeight > 0.8 * node.clientWidth ? 'horizontal' : 'vertical'});
676 | // can we reset the pane "size" when split changes?
677 | }
678 | dragFinished(newSize) {
679 | this.setState({ size: newSize });
680 | }
681 |
682 | onSubmit(value) {
683 | const lastFileName = value.split('#').shift().split('?').shift().split('/').pop();
684 | this.props.onUrlChange(value);
685 | this.setState({ url: value, lastFileName });
686 | }
687 |
688 | onSave() {
689 | const file = new Blob([this.model.getValue()], { type: 'text/plain' });
690 | const a = document.createElement('a');
691 | const url = URL.createObjectURL(file);
692 | a.href = url;
693 | a.download = this.state.lastFileName;
694 | document.body.appendChild(a);
695 | a.click();
696 | setTimeout(() => {
697 | document.body.removeChild(a);
698 | window.URL.revokeObjectURL(url);
699 | }, 0);
700 | }
701 | onLoad(fileStr, lastFileName) {
702 | this.model.setValue(fileStr);
703 | this.props.clearUrlParam();
704 | this.setState({ lastFileName });
705 | }
706 |
707 | onChecked() {
708 | this.setState({ checked: !this.state.checked });
709 | }
710 |
711 | render() {
712 | const infoStyle = {
713 | height: (this.state.size && (this.state.split === 'horizontal')) ?
714 | `calc(95vh - ${this.state.checked ? 115 : 0}px - ${this.state.size}px)` :
715 | (this.state.split === 'horizontal' ?
716 | // crude hack to set initial height if horizontal
717 | `calc(35vh - ${this.state.checked ? 45 : 0}px)` :
718 | '100%'),
719 | width: (this.state.size && (this.state.split === 'vertical')) ?
720 | `calc(98vw - ${this.state.size}px)` :
721 | (this.state.split === 'vertical' ? '38vw' : '99%'),
722 | };
723 | return (
724 |
729 |
730 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
);
739 | }
740 | }
741 |
742 | const defaultValue =
743 | `-- Live ${(self as any).WebAssembly ? 'WebAssembly' : 'JavaScript'} version of Lean
744 | #eval let v := lean.version in let s := lean.special_version_desc in string.join
745 | ["Lean (version ", v.1.repr, ".", v.2.1.repr, ".", v.2.2.repr, ", ",
746 | if s ≠ "" then s ++ ", " else s, "commit ", (lean.githash.to_list.take 12).as_string, ")"]
747 |
748 | example (m n : ℕ) : m + n = n + m :=
749 | by simp [nat.add_comm]`;
750 |
751 | interface HashParams {
752 | url: string;
753 | code: string;
754 | }
755 | function parseHash(hash: string): HashParams {
756 | hash = hash.slice(1);
757 | const hashObj = hash.split('&').map((s) => s.split('='))
758 | .reduce( (pre, [key, value]) => ({ ...pre, [key]: value }), {} ) as any;
759 | const url = decodeURIComponent(hashObj.url || '');
760 | const code = decodeURIComponent(hashObj.code || defaultValue);
761 | return { url, code };
762 | }
763 | function paramsToString(params: HashParams): string {
764 | let s = '#';
765 | if (params.url) {
766 | s = '#url=' + encodeURIComponent(params.url)
767 | .replace(/\(/g, '%28').replace(/\)/g, '%29');
768 | }
769 | // nonempty params.code will wipe out params.url
770 | if (params.code) {
771 | params.url = null;
772 | s = '#code=' + encodeURIComponent(params.code)
773 | .replace(/\(/g, '%28').replace(/\)/g, '%29');
774 | }
775 | return s;
776 | }
777 |
778 | function App() {
779 | const initUrl: URL = new URL(window.location.href);
780 | const params: HashParams = parseHash(initUrl.hash);
781 |
782 | function changeUrl(newValue, key) {
783 | params[key] = newValue;
784 | // if we just loaded a url, wipe out the code param
785 | if (key === 'url' || !newValue) { params.code = null; }
786 | history.replaceState(undefined, undefined, paramsToString(params));
787 | }
788 |
789 | function clearUrlParam() {
790 | params.url = null;
791 | history.replaceState(undefined, undefined, paramsToString(params));
792 | }
793 |
794 | const fn = monaco.Uri.file('test.lean').fsPath;
795 |
796 | if (window.localStorage.getItem('underline') === 'true') {
797 | const style = document.createElement('style');
798 | style.type = 'text/css';
799 | style.id = 'hideUnderline';
800 | style.appendChild(document.createTextNode(`.monaco-editor .greensquiggly,
801 | .monaco-editor .redsquiggly { background-size:0px; }`));
802 | document.head.appendChild(style);
803 | }
804 |
805 | if (window.localStorage.getItem('docs') === 'true') {
806 | const style = document.createElement('style');
807 | style.type = 'text/css';
808 | style.id = 'hideDocs';
809 | style.appendChild(document.createTextNode(`.toggleDoc, .doc-header { display:none; }`));
810 | document.head.appendChild(style);
811 | }
812 |
813 | return (
814 | changeUrl(newValue, 'code')}
815 | initialUrl={params.url} onUrlChange={(newValue) => changeUrl(newValue, 'url')}
816 | clearUrlParam={clearUrlParam} />
817 | );
818 | }
819 |
820 | const hostPrefix = './';
821 |
822 | const leanJsOpts: LeanJsOpts = {
823 | javascript: hostPrefix + 'lean_js_js.js',
824 | libraryZip: hostPrefix + 'library.zip',
825 | libraryMeta: hostPrefix + 'library.info.json',
826 | libraryOleanMap: hostPrefix + 'library.olean_map.json',
827 | libraryKey: 'library',
828 | webassemblyJs: hostPrefix + 'lean_js_wasm.js',
829 | webassemblyWasm: hostPrefix + 'lean_js_wasm.wasm',
830 | dbName: 'leanlibrary',
831 | };
832 |
833 | let info = null;
834 | const metaPromise = fetch(leanJsOpts.libraryMeta)
835 | .then((res) => res.json())
836 | .then((j) => info = j);
837 |
838 | // tslint:disable-next-line:no-var-requires
839 | (window as any).require(['vs/editor/editor.main'], () => {
840 | registerLeanLanguage(leanJsOpts);
841 | render(
842 | ,
843 | document.getElementById('root'),
844 | );
845 | });
846 |
--------------------------------------------------------------------------------
/src/langservice.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import * as lean from 'lean-client-js-browser';
3 | import {leanSyntax} from './syntax';
4 | import * as translations from './translations.json';
5 |
6 | export class CoalescedTimer {
7 | private timer: number = undefined;
8 | do(ms: number, f: () => void) {
9 | if (this.timer) {
10 | clearTimeout(this.timer);
11 | }
12 | this.timer = setTimeout(() => {
13 | this.timer = undefined;
14 | f();
15 | }, ms) as any;
16 | }
17 | }
18 |
19 | export class ReactiveValue {
20 | updated = new lean.Event();
21 | private lastValue: E;
22 |
23 | constructor(initialValue: E) {
24 | this.lastValue = initialValue;
25 | this.updated.on((e) => this.lastValue = e);
26 | }
27 |
28 | get value() { return this.lastValue; }
29 | }
30 |
31 | export let server: lean.Server;
32 | export let allMessages: lean.Message[] = [];
33 |
34 | export const currentlyRunning = new ReactiveValue([]);
35 | function addToRunning(fn: string) {
36 | if (currentlyRunning.value.indexOf(fn) === -1) {
37 | currentlyRunning.updated.fire([].concat([fn], currentlyRunning.value));
38 | }
39 | }
40 | function removeFromRunning(fn: string) {
41 | currentlyRunning.updated.fire(currentlyRunning.value.filter((v) => v !== fn));
42 | }
43 |
44 | const watchers = new Map();
45 |
46 | export let delayMs = 1000;
47 |
48 | class ModelWatcher implements monaco.IDisposable {
49 | private changeSubscription: monaco.IDisposable;
50 | private syncTimer = new CoalescedTimer();
51 | private version = 0;
52 |
53 | constructor(private model: monaco.editor.IModel) {
54 | this.changeSubscription = model.onDidChangeContent((e) => {
55 | completionBuffer.cancel();
56 | // this.checkInputCompletion(e);
57 | this.syncIn(delayMs);
58 | });
59 | this.syncNow();
60 | }
61 |
62 | dispose() { this.changeSubscription.dispose(); }
63 |
64 | syncIn(ms: number) {
65 | addToRunning(this.model.uri.fsPath);
66 | completionBuffer.cancel();
67 | const version = (this.version += 1);
68 | this.syncTimer.do(ms, () => {
69 | if (!server) {
70 | return;
71 | }
72 | server.sync(this.model.uri.fsPath, this.model.getValue()).then(() => {
73 | if (this.version === version) {
74 | removeFromRunning(this.model.uri.fsPath);
75 | }
76 | });
77 | });
78 | }
79 |
80 | syncNow() { this.syncIn(0); }
81 | }
82 |
83 | const triggerChars = new Set(' ,');
84 | export function checkInputCompletionChange(e: monaco.editor.IModelContentChangedEvent,
85 | editor: monaco.editor.IStandaloneCodeEditor,
86 | model: monaco.editor.IModel): void {
87 | if (e.changes.length !== 1) {
88 | return null;
89 | }
90 | const change = e.changes[0];
91 | if (change.rangeLength === 0 && triggerChars.has(change.text)) {
92 | completionEdit(editor, model, true);
93 | }
94 | return null;
95 | }
96 |
97 | // completionEdit() assumes that all these are 2 characters long!
98 | const hackyReplacements = {
99 | ['{{}}']: '⦃⦄',
100 | ['[[]]']: '⟦⟧',
101 | ['<>']: '⟨⟩',
102 | ['([])']: '⟮⟯',
103 | ['f<>']: '‹›',
104 | ['f<<>>']: '«»',
105 | };
106 | export function checkInputCompletionPosition(e: monaco.editor.ICursorPositionChangedEvent,
107 | editor: monaco.editor.IStandaloneCodeEditor,
108 | model: monaco.editor.IModel): boolean {
109 | const lineNum = e.position.lineNumber;
110 | const line = model.getLineContent(lineNum);
111 | const cursorPos = e.position.column;
112 | const index = line.lastIndexOf('\\', cursorPos - 1) + 1;
113 | const match = line.substring(index, cursorPos - 1);
114 | // ordinary completion
115 | const replaceText = index && translations[match];
116 | // hacky completions put the cursor between paired Unicode brackets
117 | const hackyReplacement = index && hackyReplacements[match];
118 | return replaceText || hackyReplacement;
119 | }
120 |
121 | function completionEdit(editor: monaco.editor.IStandaloneCodeEditor,
122 | model: monaco.editor.IModel, triggeredByTyping: boolean): void {
123 | const sel = editor.getSelections();
124 | const lineNum = sel[0].startLineNumber;
125 | const line = model.getLineContent(lineNum);
126 | const cursorPos = sel[0].startColumn;
127 | const index = line.lastIndexOf('\\', cursorPos - 1) + 1;
128 | const match = line.substring(index, cursorPos - 1);
129 | // ordinary completion
130 | const replaceText = index && translations[match];
131 | // hacky completions put the cursor between paired Unicode brackets
132 | const hackyReplacement = index && hackyReplacements[match];
133 | if (replaceText || hackyReplacement) {
134 | if (triggeredByTyping) {
135 | const range1 = new monaco.Range(lineNum, index, lineNum, cursorPos);
136 | editor.executeEdits(null, [{
137 | identifier: {major: 1, minor: 1},
138 | range: range1,
139 | text: replaceText || hackyReplacement[0],
140 | forceMoveMarkers: false,
141 | }], [new monaco.Selection(lineNum, index + 1, lineNum, index + 1)]);
142 | if (hackyReplacement) {
143 | // put the closing bracket after the typed character
144 | const range2 = new monaco.Range(lineNum, index + 2, lineNum, index + 2);
145 | editor.executeEdits(null, [{
146 | identifier: {major: 1, minor: 1},
147 | range: range2,
148 | text: hackyReplacement[1],
149 | forceMoveMarkers: false,
150 | }], [new monaco.Selection(lineNum, index + 1, lineNum, index + 1)]);
151 | }
152 | // HACK: monaco seems to move the cursor AFTER the onDidChangeModel event handlers are called,
153 | // so we move the cursor +1 character to the right so that it's immediately after the typed character
154 | // (assumes all unicode translations are 1 character long and
155 | // all hackyReplacements have a 1-character opening brace!)
156 | global.setTimeout(() => editor.setPosition(new monaco.Position(lineNum, index + 2)), 0);
157 | } else {
158 | const range = new monaco.Range(lineNum, index, lineNum, cursorPos);
159 | editor.executeEdits(null, [{
160 | identifier: {major: 1, minor: 1},
161 | range,
162 | text: replaceText || hackyReplacement,
163 | forceMoveMarkers: false,
164 | }], [new monaco.Selection(lineNum, index + 1, lineNum, index + 1)]);
165 | // index + 1: the final cursor position is one character to the right of the initial '\'
166 | // (assumes all unicode translations are 1 character long and
167 | // all hackyReplacements have a 1-character opening brace!)
168 | }
169 | }
170 | }
171 |
172 | export function tabHandler(editor: monaco.editor.IStandaloneCodeEditor,
173 | model: monaco.editor.IModel): void {
174 | completionEdit(editor, model, false);
175 | }
176 |
177 | class CompletionBuffer {
178 | private reject: (reason: any) => void;
179 | private timer;
180 |
181 | wait(ms: number): Promise {
182 | this.cancel();
183 | return new Promise((resolve, reject) => {
184 | this.reject = reject;
185 | this.timer = setTimeout(() => {
186 | this.timer = undefined;
187 | resolve();
188 | }, ms);
189 | });
190 | }
191 | cancel() {
192 | if (this.timer) {
193 | clearTimeout(this.timer);
194 | this.reject('timeout');
195 | this.timer = undefined;
196 | }
197 | }
198 | }
199 | const completionBuffer = new CompletionBuffer();
200 |
201 | function toSeverity(severity: lean.Severity): monaco.Severity {
202 | switch (severity) {
203 | case 'warning': return monaco.Severity.Warning;
204 | case 'error': return monaco.Severity.Error;
205 | case 'information': return monaco.Severity.Info;
206 | }
207 | }
208 |
209 | export function registerLeanLanguage(leanJsOpts: lean.LeanJsOpts) {
210 | if (server) {
211 | return;
212 | }
213 |
214 | const transport = new lean.WebWorkerTransport(leanJsOpts);
215 | server = new lean.Server(transport);
216 | server.error.on((err) => console.log('error:', err));
217 | server.connect();
218 | // server.logMessagesToConsole = true;
219 | server.logMessagesToConsole = window.localStorage.getItem('logging') === 'true';
220 |
221 | monaco.languages.register({
222 | id: 'lean',
223 | filenamePatterns: ['*.lean'],
224 | });
225 |
226 | monaco.editor.onDidCreateModel((model) => {
227 | if (model.getModeId() === 'lean') {
228 | watchers.set(model.uri.fsPath, new ModelWatcher(model));
229 | }
230 | });
231 | monaco.editor.onWillDisposeModel((model) => {
232 | const watcher = watchers.get(model.uri.fsPath);
233 | if (watcher) {
234 | watcher.dispose();
235 | watchers.delete(model.uri.fsPath);
236 | }
237 | });
238 |
239 | server.allMessages.on((allMsgs) => {
240 | allMessages = allMsgs.msgs;
241 | for (const model of monaco.editor.getModels()) {
242 | const fn = model.uri.fsPath;
243 | const markers: monaco.editor.IMarkerData[] = [];
244 | for (const msg of allMsgs.msgs) {
245 | if (msg.file_name !== fn) {
246 | continue;
247 | }
248 | const marker: monaco.editor.IMarkerData = {
249 | severity: toSeverity(msg.severity),
250 | message: msg.text,
251 | startLineNumber: msg.pos_line,
252 | startColumn: msg.pos_col + 1,
253 | endLineNumber: msg.pos_line,
254 | endColumn: msg.pos_col + 1,
255 | };
256 | if (msg.end_pos_line && msg.end_pos_col !== undefined) {
257 | marker.endLineNumber = msg.end_pos_line;
258 | marker.endColumn = msg.end_pos_col + 1;
259 | }
260 | markers.push(marker);
261 | }
262 | monaco.editor.setModelMarkers(model, 'lean', markers);
263 | }
264 | });
265 |
266 | monaco.languages.registerCompletionItemProvider('lean', {
267 | provideCompletionItems: (editor, position) =>
268 | completionBuffer.wait(delayMs).then(() => {
269 | watchers.get(editor.uri.fsPath).syncNow();
270 | return server.complete(editor.uri.fsPath, position.lineNumber, position.column - 1).then((result) => {
271 | const items: monaco.languages.CompletionItem[] = [];
272 | for (const compl of result.completions || []) {
273 | const item = {
274 | kind: monaco.languages.CompletionItemKind.Function,
275 | label: compl.text,
276 | detail: compl.type,
277 | documentation: compl.doc,
278 | range: new monaco.Range(position.lineNumber, position.column - result.prefix.length,
279 | position.lineNumber, position.column),
280 | };
281 | if (compl.tactic_params) {
282 | item.detail = compl.tactic_params.join(' ');
283 | }
284 | items.push(item);
285 | }
286 | return items;
287 | });
288 | }, () => undefined),
289 | });
290 |
291 | monaco.languages.registerHoverProvider('lean', {
292 | provideHover: (editor, position): Promise => {
293 | return server.info(editor.uri.fsPath, position.lineNumber, position.column - 1).then((response) => {
294 | const marked: monaco.MarkedString[] = [];
295 | const record = response.record;
296 | if (!record) {
297 | return {contents: []} as monaco.languages.Hover;
298 | }
299 | const name = record['full-id'] || record.text;
300 | if (name) {
301 | if (response.record.tactic_params) {
302 | marked.push({
303 | language: 'text',
304 | value: name + ' ' + record.tactic_params.join(' '),
305 | });
306 | } else {
307 | marked.push({
308 | language: 'lean',
309 | value: name + ' : ' + record.type,
310 | });
311 | }
312 | }
313 | if (response.record.doc) {
314 | marked.push(response.record.doc);
315 | }
316 | if (response.record.state) {
317 | marked.push({language: 'lean', value: record.state});
318 | }
319 | return {
320 | contents: marked,
321 | range: {
322 | startLineNumber: position.lineNumber,
323 | startColumn: position.column,
324 | endLineNumber: position.lineNumber,
325 | endColumn: position.column,
326 | },
327 | };
328 | });
329 | },
330 | });
331 |
332 | monaco.languages.setMonarchTokensProvider('lean', leanSyntax as any);
333 | }
334 |
--------------------------------------------------------------------------------
/src/syntax.ts:
--------------------------------------------------------------------------------
1 | export const leanSyntax = {
2 | // Set defaultToken to invalid to see what you do not tokenize yet
3 | // defaultToken: 'invalid',
4 |
5 | keywords: [
6 | 'def', 'definition', 'lemma', 'theorem', 'example', 'axiom', 'constant', 'constants',
7 | 'class', 'instance', 'structure', 'inductive', 'variable', 'variables',
8 | 'universe', 'universes',
9 | 'attribute', 'namespace', 'section', 'noncomputable',
10 | 'meta', 'mutual',
11 | 'do', 'have', 'let', 'assume', 'suffices', 'show', 'from',
12 | 'local', 'notation', 'open', 'export',
13 | 'by', 'begin', 'end',
14 | 'λ', '∀', 'Π', '∃', 'Σ',
15 | 'if', 'this', 'break', 'protected', 'private', 'else',
16 | '#print', '#check', '#eval', '#reduce', '#help', '#exit', '#unify', '#compile',
17 | 'set_option', 'import', 'prelude',
18 | ],
19 |
20 | typeKeywords: [
21 | 'Sort', 'Prop', 'Type',
22 | ],
23 |
24 | operators: [
25 | '=', '>', '<', '!', '~', '?', ':', '==', '<=', '>=', '!=',
26 | '&&', '||', '++', '--', '+', '-', '*', '/', '&', '|', '^', '%',
27 | '<<', '>>', '>>>', '+=', '-=', '*=', '/=', '&=', '|=', '^=',
28 | '%=', '<<=', '>>=', '>>>=', '→',
29 | ],
30 |
31 | // we include these common regular expressions
32 | symbols: /[=>](?!@symbols)/, '@brackets'],
57 | [/@symbols/, { cases: { '@operators': 'operator',
58 | '@default' : '' } } ],
59 |
60 | // numbers
61 | [/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
62 | [/0[xX][0-9a-fA-F]+/, 'number.hex'],
63 | [/\d+/, 'number'],
64 |
65 | // delimiter: after number because of .\d floats
66 | [/[;,.]/, 'delimiter'],
67 |
68 | // strings
69 | [/"([^"\\]|\\.)*$/, 'string.invalid' ], // non-teminated string
70 | [/"/, { token: 'string.quote', bracket: '@open', next: '@string' } ],
71 |
72 | // characters
73 | [/'[^\\']'/, 'string'],
74 | [/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
75 | [/'/, 'string.invalid'],
76 | ],
77 |
78 | comment: [
79 | [/[^-]+/, 'comment' ],
80 | [/\/-/, 'comment', '@push' ], // nested comment
81 | [/-\//, 'comment', '@pop' ],
82 | [/[-*]/, 'comment' ],
83 | ],
84 |
85 | string: [
86 | [/[^\\"]+/, 'string'],
87 | [/@escapes/, 'string.escape'],
88 | [/\\./, 'string.escape.invalid'],
89 | [/"/, { token: 'string.quote', bracket: '@close', next: '@pop' } ],
90 | ],
91 |
92 | whitespace: [
93 | [/[ \t\r\n]+/, 'white'],
94 | [/\/\-/, 'comment', '@comment' ],
95 | [/--.*$/, 'comment'],
96 | ],
97 | },
98 | };
99 |
--------------------------------------------------------------------------------
/src/translations.json:
--------------------------------------------------------------------------------
1 | {
2 | "a": "α",
3 | "b": "β",
4 | "c": "χ",
5 | "d": "↓",
6 | "e": "ε",
7 | "g": "γ",
8 | "i": "∩",
9 | "m": "μ",
10 | "n": "¬",
11 | "o": "∘",
12 | "p": "Π",
13 | "t": "▸",
14 | "r": "→",
15 | "u": "↑",
16 | "v": "∨",
17 | "x": "×",
18 | "-": "⁻¹",
19 | "~": "∼",
20 | ".": "∙",
21 | "*": "⋆",
22 | "?": "¿",
23 | "1": "₁",
24 | "2": "₂",
25 | "3": "₃",
26 | "4": "₄",
27 | "5": "₅",
28 | "6": "₆",
29 | "7": "₇",
30 | "8": "₈",
31 | "9": "₉",
32 | "0": "₀",
33 | "l": "←",
34 | "<": "⟨",
35 | ">": "⟩",
36 | "O": "Ø",
37 | "&": "⅋",
38 | "A": "𝔸",
39 | "C": "ℂ",
40 | "D": "Δ",
41 | "F": "𝔽",
42 | "G": "Γ",
43 | "H": "ℍ",
44 | "I": "⋂",
45 | "I0": "⋂₀",
46 | "K": "𝕂",
47 | "L": "Λ",
48 | "N": "ℕ",
49 | "P": "Π",
50 | "Q": "ℚ",
51 | "R": "ℝ",
52 | "S": "Σ",
53 | "U": "⋃",
54 | "U0": "⋃₀",
55 | "Z": "ℤ",
56 | "#": "♯",
57 | ":": "∶",
58 | "|": "∣",
59 | "!": "¡",
60 | "be": "β",
61 | "ga": "γ",
62 | "de": "δ",
63 | "ep": "ε",
64 | "ze": "ζ",
65 | "et": "η",
66 | "th": "θ",
67 | "io": "ι",
68 | "ka": "κ",
69 | "la": "λ",
70 | "mu": "μ",
71 | "nu": "ν",
72 | "xi": "ξ",
73 | "pi": "π",
74 | "rh": "ρ",
75 | "vsi": "ς",
76 | "si": "σ",
77 | "ta": "τ",
78 | "ph": "φ",
79 | "ch": "χ",
80 | "ps": "ψ",
81 | "om": "ω",
82 | "`A": "À",
83 | "'A": "Á",
84 | "^{A}": "Â",
85 | "~A": "Ã",
86 | "\"A": "Ä",
87 | "cC": "Ç",
88 | "`E": "È",
89 | "'E": "É",
90 | "^{E}": "Ê",
91 | "\"E": "Ë",
92 | "`I": "Ì",
93 | "'I": "Í",
94 | "^{I}": "Î",
95 | "\"I": "Ï",
96 | "~N": "Ñ",
97 | "`O": "Ò",
98 | "'O": "Ó",
99 | "^{O}": "Ô",
100 | "~O": "Õ",
101 | "\"O": "Ö",
102 | "/O": "Ø",
103 | "`U": "Ù",
104 | "'U": "Ú",
105 | "^{U}": "Û",
106 | "\"U": "Ü",
107 | "'Y": "Ý",
108 | "`a": "à",
109 | "'a": "á",
110 | "^{a}": "â",
111 | "~a": "ã",
112 | "\"a": "ä",
113 | "cc": "ç",
114 | "`e": "è",
115 | "'e": "é",
116 | "^{e}": "ê",
117 | "\"e": "ë",
118 | "`i": "ì",
119 | "'i": "í",
120 | "^{i}": "î",
121 | "\"i": "ï",
122 | "~{n}": "ñ",
123 | "`o": "ò",
124 | "'o": "ó",
125 | "^{o}": "ô",
126 | "~o": "õ",
127 | "\"o": "ö",
128 | "/o": "ø",
129 | "`u": "ù",
130 | "'u": "ú",
131 | "^{u}": "û",
132 | "\"u": "ü",
133 | "'y": "ý",
134 | "\"y": "ÿ",
135 | "/L": "Ł",
136 | "notin": "∉",
137 | "note": "♩",
138 | "not": "¬",
139 | "nomisma": "𐆎",
140 | "nin": "∉",
141 | "nni": "∌",
142 | "ni": "∋",
143 | "nattrans": "⟹",
144 | "nat_trans": "⟹",
145 | "natural": "♮",
146 | "nat": "ℕ",
147 | "naira": "₦",
148 | "nabla": "∇",
149 | "napprox": "≉",
150 | "numero": "№",
151 | "nLeftarrow": "⇍",
152 | "nLeftrightarrow": "⇎",
153 | "nRightarrow": "⇏",
154 | "nVDash": "⊯",
155 | "nVdash": "⊮",
156 | "ncong": "≇",
157 | "nearrow": "↗",
158 | "neg": "¬",
159 | "nequiv": "≢",
160 | "neq": "≠",
161 | "nexists": "∄",
162 | "ne": "≠",
163 | "ngeqq": "≱",
164 | "ngeqslant": "≱",
165 | "ngeq": "≱",
166 | "ngtr": "≯",
167 | "nleftarrow": "↚",
168 | "nleftrightarrow": "↮",
169 | "nleqq": "≰",
170 | "nleqslant": "≰",
171 | "nleq": "≰",
172 | "nless": "≮",
173 | "nmid": "∤",
174 | "nparallel": "∦",
175 | "npreceq": "⋠",
176 | "nprec": "⊀",
177 | "nrightarrow": "↛",
178 | "nshortmid": "∤",
179 | "nshortparallel": "∦",
180 | "nsimeq": "≄",
181 | "nsim": "≁",
182 | "nsubseteqq": "⊈",
183 | "nsubseteq": "⊈",
184 | "nsubset": "⊄",
185 | "nsucceq": "⋡",
186 | "nsucc": "⊁",
187 | "nsupseteqq": "⊉",
188 | "nsupseteq": "⊉",
189 | "nsupset": "⊅",
190 | "ntrianglelefteq": "⋬",
191 | "ntriangleleft": "⋪",
192 | "ntrianglerighteq": "⋭",
193 | "ntriangleright": "⋫",
194 | "nvDash": "⊭",
195 | "nvdash": "⊬",
196 | "nwarrow": "↖",
197 | "eqn": "≠",
198 | "equiv": "≃",
199 | "eqcirc": "≖",
200 | "eqcolon": "≕",
201 | "eqslantgtr": "⋝",
202 | "eqslantless": "⋜",
203 | "entails": "⊢",
204 | "en": "–",
205 | "exn": "∄",
206 | "exists": "∃",
207 | "ex": "∃",
208 | "emptyset": "∅",
209 | "empty": "∅",
210 | "em": "—",
211 | "epsilon": "ε",
212 | "eps": "ε",
213 | "euro": "€",
214 | "eta": "η",
215 | "ell": "ℓ",
216 | "iso": "≅",
217 | "in": "∈",
218 | "inn": "∉",
219 | "inter": "∩",
220 | "intercal": "⊺",
221 | "intersection": "∩",
222 | "integral": "∫",
223 | "int": "ℤ",
224 | "inv": "⁻¹",
225 | "increment": "∆",
226 | "inf": "⊓",
227 | "infi": "⨅",
228 | "infty": "∞",
229 | "iff": "↔",
230 | "imp": "→",
231 | "imath": "ı",
232 | "iota": "ι",
233 | "=n": "≠",
234 | "==n": "≢",
235 | "===": "≣",
236 | "==>": "⟹",
237 | "==": "≡",
238 | "=:": "≕",
239 | "=o": "≗",
240 | "=>n": "⇏",
241 | "=>": "⇒",
242 | "~n": "≁",
243 | "~~n": "≉",
244 | "~~~": "≋",
245 | "~~-": "≊",
246 | "~~": "≈",
247 | "~-n": "≄",
248 | "~-": "≃",
249 | "~=n": "≇",
250 | "~=": "≅",
251 | "homotopy": "∼",
252 | "hom": "⟶",
253 | "hori": "ϩ",
254 | "hookleftarrow": "↩",
255 | "hookrightarrow": "↪",
256 | "hryvnia": "₴",
257 | "heta": "ͱ",
258 | "heartsuit": "♥",
259 | "hbar": "ℏ",
260 | ":~": "∻",
261 | ":=": "≔",
262 | "::-": "∺",
263 | "::": "∷",
264 | "-~": "≂",
265 | "-|": "⊣",
266 | "-1": "⁻¹",
267 | "^-1": "⁻¹",
268 | "-2": "⁻²",
269 | "-3": "⁻³",
270 | "-:": "∹",
271 | "->n": "↛",
272 | "->": "→",
273 | "-->": "⟶",
274 | "---": "─",
275 | "--=": "═",
276 | "--_": "━",
277 | "--.": "╌",
278 | "-o": "⊸",
279 | ".=.": "≑",
280 | ".=": "≐",
281 | ".+": "∔",
282 | ".-": "∸",
283 | "...": "⋯",
284 | "(=": "≘",
285 | "(b": "⟅",
286 | "and=": "≙",
287 | "and": "∧",
288 | "an": "∧",
289 | "angle": "∟",
290 | "angstrom": "Å",
291 | "all": "∀",
292 | "allf": "∀ᶠ",
293 | "all^f": "∀ᶠ",
294 | "allm": "∀ᵐ",
295 | "all^m": "∀ᵐ",
296 | "alpha": "α",
297 | "aleph": "ℵ",
298 | "asterisk": "⁎",
299 | "ast": "∗",
300 | "asymp": "≍",
301 | "apl": "⌶",
302 | "approxeq": "≊",
303 | "approx": "≈",
304 | "aa": "å",
305 | "ae": "æ",
306 | "austral": "₳",
307 | "afghani": "؋",
308 | "amalg": "∐",
309 | "or=": "≚",
310 | "ordfeminine": "ª",
311 | "ordmasculine": "º",
312 | "or": "∨",
313 | "oplus": "⊕",
314 | "op": "ᵒᵖ",
315 | "o+": "⊕",
316 | "o--": "⊖",
317 | "o-": "⊝",
318 | "ox": "⊗",
319 | "o/": "⊘",
320 | "o.": "⊙",
321 | "oo": "⊚",
322 | "o*": "∘*",
323 | "o=": "⊜",
324 | "oe": "œ",
325 | "octagonal": "🛑",
326 | "ohm": "Ω",
327 | "ounce": "℥",
328 | "omega": "ω",
329 | "omicron": "ο",
330 | "ominus": "⊖",
331 | "odot": "⊙",
332 | "oint": "∮",
333 | "oslash": "⊘",
334 | "otimes": "⊗",
335 | "*=": "≛",
336 | "t=": "≜",
337 | "transport": "▹",
338 | "trans": "▹",
339 | "triangledown": "▿",
340 | "trianglelefteq": "⊴",
341 | "triangleleft": "◃",
342 | "triangleq": "≜",
343 | "trianglerighteq": "⊵",
344 | "triangleright": "▹",
345 | "triangle": "▵",
346 | "tr": "⬝",
347 | "tb": "◂",
348 | "twoheadleftarrow": "↞",
349 | "twoheadrightarrow": "↠",
350 | "tw": "◃",
351 | "tie": "⁀",
352 | "times": "×",
353 | "theta": "θ",
354 | "therefore": "∴",
355 | "thickapprox": "≈",
356 | "thicksim": "∼",
357 | "telephone": "℡",
358 | "tenge": "₸",
359 | "textmusicalnote": "♪",
360 | "textmu": "µ",
361 | "textfractionsolidus": "⁄",
362 | "textbaht": "฿",
363 | "textdied": "✝",
364 | "textdiscount": "⁒",
365 | "textcolonmonetary": "₡",
366 | "textcircledP": "℗",
367 | "textwon": "₩",
368 | "textnaira": "₦",
369 | "textnumero": "№",
370 | "textpeso": "₱",
371 | "textpertenthousand": "‱",
372 | "textlira": "₤",
373 | "textlquill": "⁅",
374 | "textrecipe": "℞",
375 | "textreferencemark": "※",
376 | "textrquill": "⁆",
377 | "textinterrobang": "‽",
378 | "textestimated": "℮",
379 | "textopenbullet": "◦",
380 | "tugrik": "₮",
381 | "tau": "τ",
382 | "top": "⊤",
383 | "to": "→",
384 |
385 | "to0": "→₀",
386 | "r0": "→₀",
387 | "to_0": "→₀",
388 | "r_0": "→₀",
389 | "finsupp": "→₀",
390 | "to1": "→₁",
391 | "r1": "→₁",
392 | "to_1": "→₁",
393 | "r_1": "→₁",
394 | "l1": "→₁",
395 | "to1s": "→₁ₛ",
396 | "r1s": "→₁ₛ",
397 | "to_1s": "→₁ₛ",
398 | "r_1s": "→₁ₛ",
399 | "l1simplefunc": "→₁ₛ",
400 | "toa": "→ₐ",
401 | "ra": "→ₐ",
402 | "to_a": "→ₐ",
403 | "r_a": "→ₐ",
404 | "alghom": "→ₐ",
405 | "tob": "→ᵇ",
406 | "rb": "→ᵇ",
407 | "to^b": "→ᵇ",
408 | "r^b": "→ᵇ",
409 | "boundedcontinuousfunction": "→ᵇ",
410 | "tol": "→ₗ",
411 | "rl": "→ₗ",
412 | "to_l": "→ₗ",
413 | "r_l": "→ₗ",
414 | "linearmap": "→ₗ",
415 | "tom": "→ₘ",
416 | "rm": "→ₘ",
417 | "to_m": "→ₘ",
418 | "r_m": "→ₘ",
419 | "aeeqfun": "→ₘ",
420 | "rp": "→ₚ",
421 | "to_p": "→ₚ",
422 | "r_p": "→ₚ",
423 | "dfinsupp": "→ₚ",
424 | "tos": "→ₛ",
425 | "rs": "→ₛ",
426 | "to_s": "→ₛ",
427 | "r_s": "→ₛ",
428 | "simplefunc": "→ₛ",
429 |
430 | "def=": "≝",
431 | "defs": "≙",
432 | "degree": "°",
433 | "dei": "ϯ",
434 | "delta": "δ",
435 | "doteqdot": "≑",
436 | "doteq": "≐",
437 | "dotplus": "∔",
438 | "dotsquare": "⊡",
439 | "dot": "⬝",
440 | "dong": "₫",
441 | "downarrow": "↓",
442 | "downdownarrows": "⇊",
443 | "downleftharpoon": "⇃",
444 | "downrightharpoon": "⇂",
445 | "dr-": "↘",
446 | "dr=": "⇘",
447 | "drachma": "₯",
448 | "dr": "↘",
449 | "dl-": "↙",
450 | "dl=": "⇙",
451 | "dl": "↙",
452 | "d-2": "⇊",
453 | "d-u-": "⇵",
454 | "d-|": "↧",
455 | "d-": "↓",
456 | "d==": "⟱",
457 | "d=": "⇓",
458 | "dd-": "↡",
459 | "ddagger": "‡",
460 | "ddag": "‡",
461 | "ddots": "⋱",
462 | "dz": "↯",
463 | "dib": "◆",
464 | "diw": "◇",
465 | "di.": "◈",
466 | "die": "⚀",
467 | "division": "÷",
468 | "divideontimes": "⋇",
469 | "div": "÷",
470 | "diameter": "⌀",
471 | "diamondsuit": "♢",
472 | "diamond": "⋄",
473 | "digamma": "ϝ",
474 | "di": "◆",
475 | "dagger": "†",
476 | "dag": "†",
477 | "daleth": "ℸ",
478 | "dashv": "⊣",
479 | "dh": "ð",
480 | "m=": "≞",
481 | "meet": "⊓",
482 | "member": "∈",
483 | "mem": "∈",
484 | "measuredangle": "∡",
485 | "male": "♂",
486 | "maltese": "✠",
487 | "manat": "₼",
488 | "mapsto": "↦",
489 | "mathscr{I}": "ℐ",
490 | "minus": "−",
491 | "mill": "₥",
492 | "micro": "µ",
493 | "mid": "∣",
494 | "multiplication": "×",
495 | "multimap": "⊸",
496 | "mho": "℧",
497 | "models": "⊧",
498 | "mp": "∓",
499 | "?=": "≟",
500 | "??": "⁇",
501 | "?!": "‽",
502 | "prohibited": "🛇",
503 | "prod": "∏",
504 | "propto": "∝",
505 | "precapprox": "≾",
506 | "preceq": "≼",
507 | "precnapprox": "⋨",
508 | "precnsim": "⋨",
509 | "precsim": "≾",
510 | "prec": "≺",
511 | "preim": "⁻¹'",
512 | "preimage": "⁻¹'",
513 | "prime": "′",
514 | "pr": "↣",
515 | "powerset": "𝒫",
516 | "pounds": "£",
517 | "pound": "£",
518 | "pab": "▰",
519 | "paw": "▱",
520 | "partnership": "㉐",
521 | "partial": "∂",
522 | "paragraph": "¶",
523 | "parallel": "∥",
524 | "pa": "▰",
525 | "pm": "±",
526 | "perp": "⊥",
527 | "permil": "‰",
528 | "per": "⅌",
529 | "peso": "₱",
530 | "peseta": "₧",
531 | "pilcrow": "¶",
532 | "pitchfork": "⋔",
533 | "psi": "ψ",
534 | "phi": "φ",
535 | "8<": "✂",
536 | "leqn": "≰",
537 | "leqq": "≦",
538 | "leqslant": "≤",
539 | "leq": "≤",
540 | "len": "≰",
541 | "leadsto": "↝",
542 | "leftarrowtail": "↢",
543 | "leftarrow": "←",
544 | "leftharpoondown": "↽",
545 | "leftharpoonup": "↼",
546 | "leftleftarrows": "⇇",
547 | "leftrightarrows": "⇆",
548 | "leftrightarrow": "↔",
549 | "leftrightharpoons": "⇋",
550 | "leftrightsquigarrow": "↭",
551 | "leftthreetimes": "⋋",
552 | "lessapprox": "≲",
553 | "lessdot": "⋖",
554 | "lesseqgtr": "⋚",
555 | "lesseqqgtr": "⋚",
556 | "lessgtr": "≶",
557 | "lesssim": "≲",
558 | "le": "≤",
559 | "lub": "⊔",
560 | "lr--": "⟷",
561 | "lr-n": "↮",
562 | "lr-": "↔",
563 | "lr=n": "⇎",
564 | "lr=": "⇔",
565 | "lr~": "↭",
566 | "lrcorner": "⌟",
567 | "lr": "↔",
568 | "l-2": "⇇",
569 | "l-r-": "⇆",
570 | "l--": "⟵",
571 | "l-n": "↚",
572 | "l-|": "↤",
573 | "l->": "↢",
574 | "l-": "←",
575 | "l==": "⇚",
576 | "l=n": "⇍",
577 | "l=": "⇐",
578 | "l~": "↜",
579 | "ll-": "↞",
580 | "llcorner": "⌞",
581 | "llbracket": "〚",
582 | "ll": "≪",
583 | "lbag": "⟅",
584 | "lambda": "λ",
585 | "lamda": "λ",
586 | "lam": "λ",
587 | "lari": "₾",
588 | "langle": "⟨",
589 | "lira": "₤",
590 | "lceil": "⌈",
591 | "ldots": "…",
592 | "ldq": "“",
593 | "ldata": "《",
594 | "lfloor": "⌊",
595 | "lhd": "◁",
596 | "lnapprox": "⋦",
597 | "lneqq": "≨",
598 | "lneq": "≨",
599 | "lnsim": "⋦",
600 | "lnot": "¬",
601 | "longleftarrow": "⟵",
602 | "longleftrightarrow": "⟷",
603 | "longrightarrow": "⟶",
604 | "looparrowleft": "↫",
605 | "looparrowright": "↬",
606 | "lozenge": "✧",
607 | "lq": "‘",
608 | "ltimes": "⋉",
609 | "lvertneqq": "≨",
610 | "geqn": "≱",
611 | "geqq": "≧",
612 | "geqslant": "≥",
613 | "geq": "≥",
614 | "gen": "≱",
615 | "gets": "←",
616 | "ge": "≥",
617 | "glb": "⊓",
618 | "glqq": "„",
619 | "glq": "‚",
620 | "guarani": "₲",
621 | "gangia": "ϫ",
622 | "gamma": "γ",
623 | "ggg": "⋙",
624 | "gg": "≫",
625 | "gimel": "ℷ",
626 | "gnapprox": "⋧",
627 | "gneqq": "≩",
628 | "gneq": "≩",
629 | "gnsim": "⋧",
630 | "gtrapprox": "≳",
631 | "gtrdot": "⋗",
632 | "gtreqless": "⋛",
633 | "gtreqqless": "⋛",
634 | "gtrless": "≷",
635 | "gtrsim": "≳",
636 | "gvertneqq": "≩",
637 | "grqq": "“",
638 | "grq": "‘",
639 | "<=n": "≰",
640 | "<=>n": "⇎",
641 | "<=>": "⇔",
642 | "<=": "≤",
643 | "n": "↮",
648 | "<->": "↔",
649 | "<-->": "⟷",
650 | "<--": "⟵",
651 | "<-n": "↚",
652 | "<-": "←",
653 | "<<": "⟪",
654 | ">=n": "≱",
655 | ">=": "≥",
656 | ">n": "≯",
657 | ">~nn": "≵",
658 | ">~n": "⋧",
659 | ">~": "≳",
660 | ">>": "⟫",
661 | "root": "√",
662 | "ssubn": "⊄",
663 | "ssub": "⊂",
664 | "ssupn": "⊅",
665 | "ssup": "⊃",
666 | "ssqub": "⊏",
667 | "ssqup": "⊐",
668 | "ss": "⊆",
669 | "subn": "⊈",
670 | "subseteqq": "⊆",
671 | "subseteq": "⊆",
672 | "subsetneqq": "⊊",
673 | "subsetneq": "⊊",
674 | "subset": "⊂",
675 | "sub": "⊆",
676 | "supn": "⊉",
677 | "supseteqq": "⊇",
678 | "supseteq": "⊇",
679 | "supsetneqq": "⊋",
680 | "supsetneq": "⊋",
681 | "supset": "⊃",
682 | "sup": "⊔",
683 | "supr": "⨆",
684 | "surd3": "∛",
685 | "surd4": "∜",
686 | "surd": "√",
687 | "succapprox": "≿",
688 | "succcurlyeq": "≽",
689 | "succeq": "≽",
690 | "succnapprox": "⋩",
691 | "succnsim": "⋩",
692 | "succsim": "≿",
693 | "succ": "≻",
694 | "sum": "∑",
695 | "squbn": "⋢",
696 | "squb": "⊑",
697 | "squpn": "⋣",
698 | "squp": "⊒",
699 | "square": "□",
700 | "squigarrowright": "⇝",
701 | "sqb": "■",
702 | "sqw": "□",
703 | "sq.": "▣",
704 | "sqo": "▢",
705 | "sqcap": "⊓",
706 | "sqcup": "⊔",
707 | "sqrt": "√",
708 | "sqsubseteq": "⊑",
709 | "sqsubset": "⊏",
710 | "sqsupseteq": "⊒",
711 | "sqsupset": "⊐",
712 | "sq": "◾",
713 | "sy": "⁻¹",
714 | "st4": "✦",
715 | "st6": "✶",
716 | "st8": "✴",
717 | "st12": "✹",
718 | "stigma": "ϛ",
719 | "star": "⋆",
720 | "straightphi": "φ",
721 | "st": "⋆",
722 | "spesmilo": "₷",
723 | "spadesuit": "♠",
724 | "sphericalangle": "∢",
725 | "section": "§",
726 | "searrow": "↘",
727 | "setminus": "∖",
728 | "san": "ϻ",
729 | "sampi": "ϡ",
730 | "shortmid": "∣",
731 | "shortparallel": "∥",
732 | "sho": "ϸ",
733 | "shima": "ϭ",
734 | "shei": "ϣ",
735 | "sharp": "♯",
736 | "sigma": "σ",
737 | "simeq": "≃",
738 | "sim": "∼",
739 | "sbs": "﹨",
740 | "smallamalg": "∐",
741 | "smallsetminus": "∖",
742 | "smallsmile": "⌣",
743 | "smile": "⌣",
744 | "smul": "•",
745 | "swarrow": "↙",
746 | "Tr": "◀",
747 | "Tb": "◀",
748 | "Tw": "◁",
749 | "Tau": "Τ",
750 | "Theta": "Θ",
751 | "TH": "Þ",
752 | "union": "∪",
753 | "undertie": "‿",
754 | "uncertainty": "⯑",
755 | "un": "∪",
756 | "u+": "⊎",
757 | "u.": "⊍",
758 | "ud-|": "↨",
759 | "ud-": "↕",
760 | "ud=": "⇕",
761 | "ud": "↕",
762 | "ul-": "↖",
763 | "ul=": "⇖",
764 | "ulcorner": "⌜",
765 | "ul": "↖",
766 | "ur-": "↗",
767 | "ur=": "⇗",
768 | "urcorner": "⌝",
769 | "ur": "↗",
770 | "u-2": "⇈",
771 | "u-d-": "⇅",
772 | "u-|": "↥",
773 | "u-": "↑",
774 | "u==": "⟰",
775 | "u=": "⇑",
776 | "uu-": "↟",
777 | "upsilon": "υ",
778 | "uparrow": "↑",
779 | "updownarrow": "↕",
780 | "upleftharpoon": "↿",
781 | "uplus": "⊎",
782 | "uprightharpoon": "↾",
783 | "upuparrows": "⇈",
784 | "And": "⋀",
785 | "AA": "Å",
786 | "AE": "Æ",
787 | "Alpha": "Α",
788 | "Or": "⋁",
789 | "O+": "⨁",
790 | "Ox": "⨂",
791 | "O.": "⨀",
792 | "O*": "⍟",
793 | "OE": "Œ",
794 | "Omega": "Ω",
795 | "Omicron": "Ο",
796 | "Int": "ℤ",
797 | "Inter": "⋂",
798 | "bInter": "⋂",
799 | "Iota": "Ι",
800 | "Im": "ℑ",
801 | "Un": "⋃",
802 | "Union": "⋃",
803 | "bUnion": "⋃",
804 | "U+": "⨄",
805 | "U.": "⨃",
806 | "Upsilon": "Υ",
807 | "Uparrow": "⇑",
808 | "Updownarrow": "⇕",
809 | "Gl-": "ƛ",
810 | "Gl": "λ",
811 | "Gangia": "Ϫ",
812 | "Gamma": "Γ",
813 | "Glb": "⨅",
814 | "Ga": "α",
815 | "GA": "Α",
816 | "Gb": "β",
817 | "GB": "Β",
818 | "Gg": "γ",
819 | "GG": "Γ",
820 | "Gd": "δ",
821 | "GD": "Δ",
822 | "Ge": "ε",
823 | "GE": "Ε",
824 | "Gz": "ζ",
825 | "GZ": "Ζ",
826 | "Gth": "θ",
827 | "Gt": "τ",
828 | "GTH": "Θ",
829 | "GT": "Τ",
830 | "Gi": "ι",
831 | "GI": "Ι",
832 | "Gk": "κ",
833 | "GK": "Κ",
834 | "GL": "Λ",
835 | "Gm": "μ",
836 | "GM": "Μ",
837 | "Gn": "ν",
838 | "GN": "Ν",
839 | "Gx": "ξ",
840 | "GX": "Ξ",
841 | "Gr": "ρ",
842 | "GR": "Ρ",
843 | "Gs": "σ",
844 | "GS": "Σ",
845 | "Gu": "υ",
846 | "GU": "Υ",
847 | "Gf": "φ",
848 | "GF": "Φ",
849 | "Gc": "χ",
850 | "GC": "Χ",
851 | "Gp": "ψ",
852 | "GP": "Ψ",
853 | "Go": "ω",
854 | "GO": "Ω",
855 | "Inf": "⨅",
856 | "Join": "⨆",
857 | "Lub": "⨆",
858 | "Lambda": "Λ",
859 | "Lamda": "Λ",
860 | "Leftarrow": "⇐",
861 | "Leftrightarrow": "⇔",
862 | "Letter": "✉",
863 | "Lleftarrow": "⇚",
864 | "Ll": "⋘",
865 | "Longleftarrow": "⇐",
866 | "Longleftrightarrow": "⇔",
867 | "Longrightarrow": "⇒",
868 | "Meet": "⨅",
869 | "Sup": "⨆",
870 | "Sqcap": "⨅",
871 | "Sqcup": "⨆",
872 | "Lsh": "↰",
873 | "|-n": "⊬",
874 | "|-": "⊢",
875 | "|=n": "⊭",
876 | "|=": "⊨",
877 | "||-n": "⊮",
878 | "||-": "⊩",
879 | "||=n": "⊯",
880 | "||=": "⊫",
881 | "|||-": "⊪",
882 | "||n": "∦",
883 | "||": "∥",
884 | "|n": "∤",
885 | "Com": "ℂ",
886 | "Chi": "Χ",
887 | "Cap": "⋒",
888 | "Cup": "⋓",
889 | "cul": "⌜",
890 | "cuL": "⌈",
891 | "currency": "¤",
892 | "curlyeqprec": "⋞",
893 | "curlyeqsucc": "⋟",
894 | "curlypreceq": "≼",
895 | "curlyvee": "⋎",
896 | "curlywedge": "⋏",
897 | "curvearrowleft": "↶",
898 | "curvearrowright": "↷",
899 | "cur": "⌝",
900 | "cuR": "⌉",
901 | "cup": "∪",
902 | "cu": "⌜",
903 | "cll": "⌞",
904 | "clL": "⌊",
905 | "clr": "⌟",
906 | "clR": "⌋",
907 | "clubsuit": "♣",
908 | "cl": "⌞",
909 | "construction": "🚧",
910 | "cong": "≅",
911 | "con": "⬝",
912 | "compl": "ᶜ",
913 | "complement": "ᶜ",
914 | "complementprefix": "∁",
915 | "Complement": "∁",
916 | "comp": "∘",
917 | "com": "ℂ",
918 | "coloneq": "≔",
919 | "colon": "₡",
920 | "copyright": "©",
921 | "cdots": "⋯",
922 | "cdot": "⬝",
923 | "cib": "●",
924 | "ciw": "○",
925 | "ci..": "◌",
926 | "ci.": "◎",
927 | "ciO": "◯",
928 | "circeq": "≗",
929 | "circlearrowleft": "↺",
930 | "circlearrowright": "↻",
931 | "circledR": "®",
932 | "circledS": "Ⓢ",
933 | "circledast": "⊛",
934 | "circledcirc": "⊚",
935 | "circleddash": "⊝",
936 | "circ": "∘",
937 | "ci": "●",
938 | "centerdot": "·",
939 | "cent": "¢",
940 | "cedi": "₵",
941 | "celsius": "℃",
942 | "ce": "ȩ",
943 | "checkmark": "✓",
944 | "chi": "χ",
945 | "cruzeiro": "₢",
946 | "caution": "☡",
947 | "cap": "∩",
948 | "qed": "∎",
949 | "quad": " ",
950 | "+ ": "⊹",
951 | "b+": "⊞",
952 | "b-": "⊟",
953 | "bx": "⊠",
954 | "b.": "⊡",
955 | "bn": "ℕ",
956 | "bz": "ℤ",
957 | "bq": "ℚ",
958 | "brokenbar": "¦",
959 | "br": "ℝ",
960 | "bc": "ℂ",
961 | "bp": "ℙ",
962 | "bb": "𝔹",
963 | "bsum": "⅀",
964 | "b0": "𝟘",
965 | "b1": "𝟙",
966 | "b2": "𝟚",
967 | "b3": "𝟛",
968 | "b4": "𝟜",
969 | "b5": "𝟝",
970 | "b6": "𝟞",
971 | "b7": "𝟟",
972 | "b8": "𝟠",
973 | "b9": "𝟡",
974 | "sb0": "𝟬",
975 | "sb1": "𝟭",
976 | "sb2": "𝟮",
977 | "sb3": "𝟯",
978 | "sb4": "𝟰",
979 | "sb5": "𝟱",
980 | "sb6": "𝟲",
981 | "sb7": "𝟳",
982 | "sb8": "𝟴",
983 | "sb9": "𝟵",
984 | "bub": "•",
985 | "buw": "◦",
986 | "but": "‣",
987 | "bumpeq": "≏",
988 | "bu": "•",
989 | "biohazard": "☣",
990 | "bigcap": "⋂",
991 | "bigcirc": "◯",
992 | "bigcoprod": "∐",
993 | "bigcup": "⋃",
994 | "bigglb": "⨅",
995 | "biginf": "⨅",
996 | "bigjoin": "⨆",
997 | "biglub": "⨆",
998 | "bigmeet": "⨅",
999 | "bigsqcap": "⨅",
1000 | "bigsqcup": "⨆",
1001 | "bigstar": "★",
1002 | "bigsup": "⨆",
1003 | "bigtriangledown": "▽",
1004 | "bigtriangleup": "△",
1005 | "bigvee": "⋁",
1006 | "bigwedge": "⋀",
1007 | "beta": "β",
1008 | "beth": "ℶ",
1009 | "between": "≬",
1010 | "because": "∵",
1011 | "backcong": "≌",
1012 | "backepsilon": "∍",
1013 | "backprime": "‵",
1014 | "backsimeq": "⋍",
1015 | "backsim": "∽",
1016 | "barwedge": "⊼",
1017 | "blacklozenge": "✦",
1018 | "blacksquare": "▪",
1019 | "blacksmiley": "☻",
1020 | "blacktriangledown": "▾",
1021 | "blacktriangleleft": "◂",
1022 | "blacktriangleright": "▸",
1023 | "blacktriangle": "▴",
1024 | "bot": "⊥",
1025 | "bowtie": "⋈",
1026 | "boxminus": "⊟",
1027 | "boxmid": "◫",
1028 | "hcomp": "◫",
1029 | "boxplus": "⊞",
1030 | "boxtimes": "⊠",
1031 | "join": "⊔",
1032 | "r-2": "⇉",
1033 | "r-3": "⇶",
1034 | "r-l-": "⇄",
1035 | "r--": "⟶",
1036 | "r-n": "↛",
1037 | "r-|": "↦",
1038 | "r->": "↣",
1039 | "r-o": "⊸",
1040 | "r-": "→",
1041 | "r==": "⇛",
1042 | "r=n": "⇏",
1043 | "r=": "⇒",
1044 | "r~": "↝",
1045 | "rr-": "↠",
1046 | "reb": "▬",
1047 | "rew": "▭",
1048 | "real": "ℝ",
1049 | "registered": "®",
1050 | "re": "▬",
1051 | "rbag": "⟆",
1052 | "rat": "ℚ",
1053 | "radioactive": "☢",
1054 | "rangle": "⟩",
1055 | "rq": "’",
1056 | "rial": "﷼",
1057 | "rightarrowtail": "↣",
1058 | "rightarrow": "→",
1059 | "rightharpoondown": "⇁",
1060 | "rightharpoonup": "⇀",
1061 | "rightleftarrows": "⇄",
1062 | "rightleftharpoons": "⇌",
1063 | "rightrightarrows": "⇉",
1064 | "rightthreetimes": "⋌",
1065 | "risingdotseq": "≓",
1066 | "ruble": "₽",
1067 | "rupee": "₨",
1068 | "rho": "ρ",
1069 | "rhd": "▷",
1070 | "rceil": "⌉",
1071 | "rfloor": "⌋",
1072 | "rtimes": "⋊",
1073 | "rdq": "”",
1074 | "rdata": "》",
1075 | "functor": "⥤",
1076 | "fun": "λ",
1077 | "f<<": "«",
1078 | "f<": "‹",
1079 | "f>>": "»",
1080 | "f>": "›",
1081 | "frac12": "½",
1082 | "frac13": "⅓",
1083 | "frac14": "¼",
1084 | "frac15": "⅕",
1085 | "frac16": "⅙",
1086 | "frac18": "⅛",
1087 | "frac1": "⅟",
1088 | "frac23": "⅔",
1089 | "frac25": "⅖",
1090 | "frac34": "¾",
1091 | "frac35": "⅗",
1092 | "frac38": "⅜",
1093 | "frac45": "⅘",
1094 | "frac56": "⅚",
1095 | "frac58": "⅝",
1096 | "frac78": "⅞",
1097 | "frac": "¼",
1098 | "frown": "⌢",
1099 | "frqq": "»",
1100 | "frq": "›",
1101 | "female": "♀",
1102 | "fei": "ϥ",
1103 | "facsimile": "℻",
1104 | "fallingdotseq": "≒",
1105 | "flat": "♭",
1106 | "flqq": "«",
1107 | "flq": "‹",
1108 | "forall": "∀",
1109 | ")b": "⟆",
1110 | "[[": "⟦",
1111 | "]]": "⟧",
1112 | "{{": "⦃",
1113 | "}}": "⦄",
1114 | "([": "⟮",
1115 | "])": "⟯",
1116 | "Xi": "Ξ",
1117 | "Nat": "ℕ",
1118 | "Nu": "Ν",
1119 | "Zeta": "Ζ",
1120 | "Rat": "ℚ",
1121 | "Real": "ℝ",
1122 | "Re": "ℜ",
1123 | "Rho": "Ρ",
1124 | "Rightarrow": "⇒",
1125 | "Rrightarrow": "⇛",
1126 | "Rsh": "↱",
1127 | "Fei": "Ϥ",
1128 | "Frowny": "☹",
1129 | "Hori": "Ϩ",
1130 | "Heta": "Ͱ",
1131 | "Khei": "Ϧ",
1132 | "Koppa": "Ϟ",
1133 | "Kappa": "Κ",
1134 | "^a": "ᵃ",
1135 | "^b": "ᵇ",
1136 | "^c": "ᶜ",
1137 | "^d": "ᵈ",
1138 | "^e": "ᵉ",
1139 | "^f": "ᶠ",
1140 | "^g": "ᵍ",
1141 | "^h": "ʰ",
1142 | "^i": "ⁱ",
1143 | "^j": "ʲ",
1144 | "^k": "ᵏ",
1145 | "^l": "ˡ",
1146 | "^m": "ᵐ",
1147 | "^n": "ⁿ",
1148 | "^o": "ᵒ",
1149 | "^p": "ᵖ",
1150 | "^r": "ʳ",
1151 | "^s": "ˢ",
1152 | "^t": "ᵗ",
1153 | "^u": "ᵘ",
1154 | "^v": "ᵛ",
1155 | "^w": "ʷ",
1156 | "^x": "ˣ",
1157 | "^y": "ʸ",
1158 | "^z": "ᶻ",
1159 | "^A": "ᴬ",
1160 | "^B": "ᴮ",
1161 | "^D": "ᴰ",
1162 | "^E": "ᴱ",
1163 | "^G": "ᴳ",
1164 | "^H": "ᴴ",
1165 | "^I": "ᴵ",
1166 | "^J": "ᴶ",
1167 | "^K": "ᴷ",
1168 | "^L": "ᴸ",
1169 | "^M": "ᴹ",
1170 | "^N": "ᴺ",
1171 | "^O": "ᴼ",
1172 | "^P": "ᴾ",
1173 | "^R": "ᴿ",
1174 | "^T": "ᵀ",
1175 | "^U": "ᵁ",
1176 | "^V": "ⱽ",
1177 | "^W": "ᵂ",
1178 | "^0": "⁰",
1179 | "^1": "¹",
1180 | "^2": "²",
1181 | "^3": "³",
1182 | "^4": "⁴",
1183 | "^5": "⁵",
1184 | "^6": "⁶",
1185 | "^7": "⁷",
1186 | "^8": "⁸",
1187 | "^9": "⁹",
1188 | "^)": "⁾",
1189 | "^(": "⁽",
1190 | "^=": "⁼",
1191 | "^+": "⁺",
1192 | "^o_": "º",
1193 | "^-": "⁻",
1194 | "^a_": "ª",
1195 | "^uhook": "ꭟ",
1196 | "^ubar": "ᶶ",
1197 | "^upsilon": "ᶷ",
1198 | "^ltilde": "ꭞ",
1199 | "^ls": "ꭝ",
1200 | "^lhook": "ᶪ",
1201 | "^lretroflexhook": "ᶩ",
1202 | "^oe": "ꟹ",
1203 | "^heng": "ꭜ",
1204 | "^hhook": "ʱ",
1205 | "^hwithhook": "ʱ",
1206 | "^Hstroke": "ꟸ",
1207 | "^theta": "ᶿ",
1208 | "^turnedv": "ᶺ",
1209 | "^turnedmleg": "ᶭ",
1210 | "^turnedm": "ᵚ",
1211 | "^turnedh": "ᶣ",
1212 | "^turnedalpha": "ᶛ",
1213 | "^turnedae": "ᵆ",
1214 | "^turneda": "ᵄ",
1215 | "^turnedi": "ᵎ",
1216 | "^turnede": "ᵌ",
1217 | "^turnedrhook": "ʵ",
1218 | "^turnedrwithhook": "ʵ",
1219 | "^turnedr": "ʴ",
1220 | "^twithpalatalhook": "ᶵ",
1221 | "^otop": "ᵔ",
1222 | "^ezh": "ᶾ",
1223 | "^esh": "ᶴ",
1224 | "^eth": "ᶞ",
1225 | "^eng": "ᵑ",
1226 | "^zcurl": "ᶽ",
1227 | "^zretroflexhook": "ᶼ",
1228 | "^vhook": "ᶹ",
1229 | "^Ismall": "ᶦ",
1230 | "^Lsmall": "ᶫ",
1231 | "^Nsmall": "ᶰ",
1232 | "^Usmall": "ᶸ",
1233 | "^Istroke": "ᶧ",
1234 | "^Rinverted": "ʶ",
1235 | "^ccurl": "ᶝ",
1236 | "^chi": "ᵡ",
1237 | "^shook": "ᶳ",
1238 | "^gscript": "ᶢ",
1239 | "^schwa": "ᵊ",
1240 | "^usideways": "ᵙ",
1241 | "^phi": "ᶲ",
1242 | "^obarred": "ᶱ",
1243 | "^beta": "ᵝ",
1244 | "^obottom": "ᵕ",
1245 | "^nretroflexhook": "ᶯ",
1246 | "^nlefthook": "ᶮ",
1247 | "^mhook": "ᶬ",
1248 | "^jtail": "ᶨ",
1249 | "^iota": "ᶥ",
1250 | "^istroke": "ᶤ",
1251 | "^ereversedopen": "ᶟ",
1252 | "^stop": "ˤ",
1253 | "^varphi": "ᵠ",
1254 | "^vargamma": "ᵞ",
1255 | "^gamma": "ˠ",
1256 | "^ain": "ᵜ",
1257 | "^alpha": "ᵅ",
1258 | "^oopen": "ᵓ",
1259 | "^eopen": "ᵋ",
1260 | "^Ou": "ᴽ",
1261 | "^Nreversed": "ᴻ",
1262 | "^Ereversed": "ᴲ",
1263 | "^Bbarred": "ᴯ",
1264 | "^Ae": "ᴭ",
1265 | "^SM": "℠",
1266 | "^TEL": "℡",
1267 | "^TM": "™",
1268 | "_a": "ₐ",
1269 | "_e": "ₑ",
1270 | "_h": "ₕ",
1271 | "_i": "ᵢ",
1272 | "_j": "ⱼ",
1273 | "_k": "ₖ",
1274 | "_l": "ₗ",
1275 | "_m": "ₘ",
1276 | "_n": "ₙ",
1277 | "_o": "ₒ",
1278 | "_p": "ₚ",
1279 | "_r": "ᵣ",
1280 | "_s": "ₛ",
1281 | "_t": "ₜ",
1282 | "_u": "ᵤ",
1283 | "_v": "ᵥ",
1284 | "_x": "ₓ",
1285 | "_0": "₀",
1286 | "_1": "₁",
1287 | "_2": "₂",
1288 | "_3": "₃",
1289 | "_4": "₄",
1290 | "_5": "₅",
1291 | "_6": "₆",
1292 | "_7": "₇",
1293 | "_8": "₈",
1294 | "_9": "₉",
1295 | "_)": "₎",
1296 | "_(": "₍",
1297 | "_=": "₌",
1298 | "_+": "₊",
1299 | "_--": "̲",
1300 | "_-": "₋",
1301 | "!!": "‼",
1302 | "!?": "⁉",
1303 | "San": "Ϻ",
1304 | "Sampi": "Ϡ",
1305 | "Sho": "Ϸ",
1306 | "Shima": "Ϭ",
1307 | "Shei": "Ϣ",
1308 | "Stigma": "Ϛ",
1309 | "Sigma": "Σ",
1310 | "Subset": "⋐",
1311 | "Supset": "⋑",
1312 | "Smiley": "☺",
1313 | "Psi": "Ψ",
1314 | "Phi": "Φ",
1315 | "Pi": "Π",
1316 |
1317 | "Pi0": "Π₀",
1318 | "P0": "Π₀",
1319 | "Pi_0": "Π₀",
1320 | "P_0": "Π₀",
1321 |
1322 | "bfA": "𝐀",
1323 | "bfB": "𝐁",
1324 | "bfC": "𝐂",
1325 | "bfD": "𝐃",
1326 | "bfE": "𝐄",
1327 | "bfF": "𝐅",
1328 | "bfG": "𝐆",
1329 | "bfH": "𝐇",
1330 | "bfI": "𝐈",
1331 | "bfJ": "𝐉",
1332 | "bfK": "𝐊",
1333 | "bfL": "𝐋",
1334 | "bfM": "𝐌",
1335 | "bfN": "𝐍",
1336 | "bfO": "𝐎",
1337 | "bfP": "𝐏",
1338 | "bfQ": "𝐐",
1339 | "bfR": "𝐑",
1340 | "bfS": "𝐒",
1341 | "bfT": "𝐓",
1342 | "bfU": "𝐔",
1343 | "bfV": "𝐕",
1344 | "bfW": "𝐖",
1345 | "bfX": "𝐗",
1346 | "bfY": "𝐘",
1347 | "bfZ": "𝐙",
1348 |
1349 | "bfa": "𝐚",
1350 | "bfb": "𝐛",
1351 | "bfc": "𝐜",
1352 | "bfd": "𝐝",
1353 | "bfe": "𝐞",
1354 | "bff": "𝐟",
1355 | "bfg": "𝐠",
1356 | "bfh": "𝐡",
1357 | "bfi": "𝐢",
1358 | "bfj": "𝐣",
1359 | "bfk": "𝐤",
1360 | "bfl": "𝐥",
1361 | "bfm": "𝐦",
1362 | "bfn": "𝐧",
1363 | "bfo": "𝐨",
1364 | "bfp": "𝐩",
1365 | "bfq": "𝐪",
1366 | "bfr": "𝐫",
1367 | "bfs": "𝐬",
1368 | "bft": "𝐭",
1369 | "bfu": "𝐮",
1370 | "bfv": "𝐯",
1371 | "bfw": "𝐰",
1372 | "bfx": "𝐱",
1373 | "bfy": "𝐲",
1374 | "bfz": "𝐳",
1375 |
1376 | "MiA": "𝐴",
1377 | "MiB": "𝐵",
1378 | "MiC": "𝐶",
1379 | "MiD": "𝐷",
1380 | "MiE": "𝐸",
1381 | "MiF": "𝐹",
1382 | "MiG": "𝐺",
1383 | "MiH": "𝐻",
1384 | "MiI": "𝐼",
1385 | "MiJ": "𝐽",
1386 | "MiK": "𝐾",
1387 | "MiL": "𝐿",
1388 | "MiM": "𝑀",
1389 | "MiN": "𝑁",
1390 | "MiO": "𝑂",
1391 | "MiP": "𝑃",
1392 | "MiQ": "𝑄",
1393 | "MiR": "𝑅",
1394 | "MiS": "𝑆",
1395 | "MiT": "𝑇",
1396 | "MiU": "𝑈",
1397 | "MiV": "𝑉",
1398 | "MiW": "𝑊",
1399 | "MiX": "𝑋",
1400 | "MiY": "𝑌",
1401 | "MiZ": "𝑍",
1402 | "Mia": "𝑎",
1403 | "Mib": "𝑏",
1404 | "Mic": "𝑐",
1405 | "Mid": "𝑑",
1406 | "Mie": "𝑒",
1407 | "Mif": "𝑓",
1408 | "Mig": "𝑔",
1409 | "Mii": "𝑖",
1410 | "Mij": "𝑗",
1411 | "Mik": "𝑘",
1412 | "Mil": "𝑙",
1413 | "Mim": "𝑚",
1414 | "Min": "𝑛",
1415 | "Mio": "𝑜",
1416 | "Mip": "𝑝",
1417 | "Miq": "𝑞",
1418 | "Mir": "𝑟",
1419 | "Mis": "𝑠",
1420 | "Mit": "𝑡",
1421 | "Miu": "𝑢",
1422 | "Miv": "𝑣",
1423 | "Miw": "𝑤",
1424 | "Mix": "𝑥",
1425 | "Miy": "𝑦",
1426 | "Miz": "𝑧",
1427 | "MIA": "𝑨",
1428 | "MIB": "𝑩",
1429 | "MIC": "𝑪",
1430 | "MID": "𝑫",
1431 | "MIE": "𝑬",
1432 | "MIF": "𝑭",
1433 | "MIG": "𝑮",
1434 | "MIH": "𝑯",
1435 | "MII": "𝑰",
1436 | "MIJ": "𝑱",
1437 | "MIK": "𝑲",
1438 | "MIL": "𝑳",
1439 | "MIM": "𝑴",
1440 | "MIN": "𝑵",
1441 | "MIO": "𝑶",
1442 | "MIP": "𝑷",
1443 | "MIQ": "𝑸",
1444 | "MIR": "𝑹",
1445 | "MIS": "𝑺",
1446 | "MIT": "𝑻",
1447 | "MIU": "𝑼",
1448 | "MIV": "𝑽",
1449 | "MIW": "𝑾",
1450 | "MIX": "𝑿",
1451 | "MIY": "𝒀",
1452 | "MIZ": "𝒁",
1453 | "MIa": "𝒂",
1454 | "MIb": "𝒃",
1455 | "MIc": "𝒄",
1456 | "MId": "𝒅",
1457 | "MIe": "𝒆",
1458 | "MIf": "𝒇",
1459 | "MIg": "𝒈",
1460 | "MIh": "𝒉",
1461 | "MIi": "𝒊",
1462 | "MIj": "𝒋",
1463 | "MIk": "𝒌",
1464 | "MIl": "𝒍",
1465 | "MIm": "𝒎",
1466 | "MIn": "𝒏",
1467 | "MIo": "𝒐",
1468 | "MIp": "𝒑",
1469 | "MIq": "𝒒",
1470 | "MIr": "𝒓",
1471 | "MIs": "𝒔",
1472 | "MIt": "𝒕",
1473 | "MIu": "𝒖",
1474 | "MIv": "𝒗",
1475 | "MIw": "𝒘",
1476 | "MIx": "𝒙",
1477 | "MIy": "𝒚",
1478 | "MIz": "𝒛",
1479 | "McA": "𝒜",
1480 | "McB": "ℬ",
1481 | "McC": "𝒞",
1482 | "McD": "𝒟",
1483 | "McE": "ℰ",
1484 | "McF": "ℱ",
1485 | "McG": "𝒢",
1486 | "McH": "ℋ",
1487 | "McI": "ℐ",
1488 | "McJ": "𝒥",
1489 | "McK": "𝒦",
1490 | "McL": "ℒ",
1491 | "McM": "ℳ",
1492 | "McN": "𝒩",
1493 | "McO": "𝒪",
1494 | "McP": "𝒫",
1495 | "McQ": "𝒬",
1496 | "McR": "ℛ",
1497 | "McS": "𝒮",
1498 | "McT": "𝒯",
1499 | "McU": "𝒰",
1500 | "McV": "𝒱",
1501 | "McW": "𝒲",
1502 | "McX": "𝒳",
1503 | "McY": "𝒴",
1504 | "McZ": "𝒵",
1505 | "Mca": "𝒶",
1506 | "Mcb": "𝒷",
1507 | "Mcc": "𝒸",
1508 | "Mcd": "𝒹",
1509 | "Mce": "ℯ",
1510 | "Mcf": "𝒻",
1511 | "Mcg": "ℊ",
1512 | "Mch": "𝒽",
1513 | "Mci": "𝒾",
1514 | "Mcj": "𝒿",
1515 | "Mck": "𝓀",
1516 | "Mcl": "𝓁",
1517 | "Mcm": "𝓂",
1518 | "Mcn": "𝓃",
1519 | "Mco": "ℴ",
1520 | "Mcp": "𝓅",
1521 | "Mcq": "𝓆",
1522 | "Mcr": "𝓇",
1523 | "Mcs": "𝓈",
1524 | "Mct": "𝓉",
1525 | "Mcu": "𝓊",
1526 | "Mcv": "𝓋",
1527 | "Mcw": "𝓌",
1528 | "Mcx": "𝓍",
1529 | "Mcy": "𝓎",
1530 | "Mcz": "𝓏",
1531 | "MCA": "𝓐",
1532 | "MCB": "𝓑",
1533 | "MCC": "𝓒",
1534 | "MCD": "𝓓",
1535 | "MCE": "𝓔",
1536 | "MCF": "𝓕",
1537 | "MCG": "𝓖",
1538 | "MCH": "𝓗",
1539 | "MCI": "𝓘",
1540 | "MCJ": "𝓙",
1541 | "MCK": "𝓚",
1542 | "MCL": "𝓛",
1543 | "MCM": "𝓜",
1544 | "MCN": "𝓝",
1545 | "MCO": "𝓞",
1546 | "MCP": "𝓟",
1547 | "MCQ": "𝓠",
1548 | "MCR": "𝓡",
1549 | "MCS": "𝓢",
1550 | "MCT": "𝓣",
1551 | "MCU": "𝓤",
1552 | "MCV": "𝓥",
1553 | "MCW": "𝓦",
1554 | "MCX": "𝓧",
1555 | "MCY": "𝓨",
1556 | "MCZ": "𝓩",
1557 | "MCa": "𝓪",
1558 | "MCb": "𝓫",
1559 | "MCc": "𝓬",
1560 | "MCd": "𝓭",
1561 | "MCe": "𝓮",
1562 | "MCf": "𝓯",
1563 | "MCg": "𝓰",
1564 | "MCh": "𝓱",
1565 | "MCi": "𝓲",
1566 | "MCj": "𝓳",
1567 | "MCk": "𝓴",
1568 | "MCl": "𝓵",
1569 | "MCm": "𝓶",
1570 | "MCn": "𝓷",
1571 | "MCo": "𝓸",
1572 | "MCp": "𝓹",
1573 | "MCq": "𝓺",
1574 | "MCr": "𝓻",
1575 | "MCs": "𝓼",
1576 | "MCt": "𝓽",
1577 | "MCu": "𝓾",
1578 | "MCv": "𝓿",
1579 | "MCw": "𝔀",
1580 | "MCx": "𝔁",
1581 | "MCy": "𝔂",
1582 | "MCz": "𝔃",
1583 | "MfA": "𝔄",
1584 | "MfB": "𝔅",
1585 | "MfC": "ℭ",
1586 | "MfD": "𝔇",
1587 | "MfE": "𝔈",
1588 | "MfF": "𝔉",
1589 | "MfG": "𝔊",
1590 | "MfH": "ℌ",
1591 | "MfI": "ℑ",
1592 | "MfJ": "𝔍",
1593 | "MfK": "𝔎",
1594 | "MfL": "𝔏",
1595 | "MfM": "𝔐",
1596 | "MfN": "𝔑",
1597 | "MfO": "𝔒",
1598 | "MfP": "𝔓",
1599 | "MfQ": "𝔔",
1600 | "MfR": "ℜ",
1601 | "MfS": "𝔖",
1602 | "MfT": "𝔗",
1603 | "MfU": "𝔘",
1604 | "MfV": "𝔙",
1605 | "MfW": "𝔚",
1606 | "MfX": "𝔛",
1607 | "MfY": "𝔜",
1608 | "MfZ": "ℨ",
1609 | "Mfa": "𝔞",
1610 | "Mfb": "𝔟",
1611 | "Mfc": "𝔠",
1612 | "Mfd": "𝔡",
1613 | "Mfe": "𝔢",
1614 | "Mff": "𝔣",
1615 | "Mfg": "𝔤",
1616 | "Mfh": "𝔥",
1617 | "Mfi": "𝔦",
1618 | "Mfj": "𝔧",
1619 | "Mfk": "𝔨",
1620 | "Mfl": "𝔩",
1621 | "Mfm": "𝔪",
1622 | "Mfn": "𝔫",
1623 | "Mfo": "𝔬",
1624 | "Mfp": "𝔭",
1625 | "Mfq": "𝔮",
1626 | "Mfr": "𝔯",
1627 | "Mfs": "𝔰",
1628 | "Mft": "𝔱",
1629 | "Mfu": "𝔲",
1630 | "Mfv": "𝔳",
1631 | "Mfw": "𝔴",
1632 | "Mfx": "𝔵",
1633 | "Mfy": "𝔶",
1634 | "Mfz": "𝔷",
1635 |
1636 | "yen": "¥",
1637 | "varrho": "ϱ",
1638 | "varkappa": "ϰ",
1639 | "varkai": "ϗ",
1640 | "varpi": "ϖ",
1641 | "varphi": "ϕ",
1642 | "varprime": "′",
1643 | "varpropto": "∝",
1644 | "vartheta": "ϑ",
1645 | "vartriangleleft": "⊲",
1646 | "vartriangleright": "⊳",
1647 | "varbeta": "ϐ",
1648 | "varsigma": "ς",
1649 | "veebar": "⊻",
1650 | "vee": "∨",
1651 | "ve": "ě",
1652 | "vE": "Ě",
1653 | "vdash": "⊢",
1654 | "vdots": "⋮",
1655 | "vd": "ď",
1656 | "vDash": "⊨",
1657 | "vD": "Ď",
1658 | "vc": "č",
1659 | "vC": "Č",
1660 | "koppa": "ϟ",
1661 | "kip": "₭",
1662 | "ki": "į",
1663 | "kI": "Į",
1664 | "kelvin": "K",
1665 | "kappa": "κ",
1666 | "khei": "ϧ",
1667 | "warning": "⚠",
1668 | "won": "₩",
1669 | "wedge": "∧",
1670 | "wp": "℘",
1671 | "wr": "≀",
1672 | "Dei": "Ϯ",
1673 | "Delta": "Δ",
1674 | "Digamma": "Ϝ",
1675 | "Diamond": "◇",
1676 | "Downarrow": "⇓",
1677 | "DH": "Ð",
1678 | "zeta": "ζ",
1679 | "Eta": "Η",
1680 | "Epsilon": "Ε",
1681 | "Beta": "Β",
1682 | "Box": "□",
1683 | "Bumpeq": "≎",
1684 |
1685 | "bbA": "𝔸",
1686 | "bbB": "𝔹",
1687 | "bbC": "ℂ",
1688 | "bbD": "𝔻",
1689 | "bbE": "𝔼",
1690 | "bbF": "𝔽",
1691 | "bbG": "𝔾",
1692 | "bbH": "ℍ",
1693 | "bbI": "𝕀",
1694 | "bbJ": "𝕁",
1695 | "bbK": "𝕂",
1696 | "bbL": "𝕃",
1697 | "bbM": "𝕄",
1698 | "bbN": "ℕ",
1699 | "bbO": "𝕆",
1700 | "bbP": "ℙ",
1701 | "bbQ": "ℚ",
1702 | "bbR": "ℝ",
1703 | "bbS": "𝕊",
1704 | "bbT": "𝕋",
1705 | "bbU": "𝕌",
1706 | "bbV": "𝕍",
1707 | "bbW": "𝕎",
1708 | "bbX": "𝕏",
1709 | "bbY": "𝕐",
1710 | "bbZ": "ℤ",
1711 | "bbk": "𝕜",
1712 |
1713 | "Rge0": "ℝ≥0",
1714 | "R>=0": "ℝ≥0",
1715 | "nnreal": "ℝ≥0",
1716 | "Zsqrt": "ℤ√",
1717 | "zsqrtd": "ℤ√",
1718 | "liel": "⁅",
1719 | "[-": "⁅",
1720 | "bracketl": "⁅",
1721 | "lier": "⁆",
1722 | "-]": "⁆",
1723 | "bracketr": "⁆",
1724 | "nhds": "𝓝",
1725 | "nbhds": "𝓝",
1726 | "X": "⨯",
1727 | "vectorproduct": "⨯",
1728 | "crossproduct": "⨯",
1729 | "coprod": "⨿",
1730 | "sigmaobj": "∐",
1731 | "xf": "×ᶠ",
1732 | "exf": "∃ᶠ",
1733 | "c[": "⦃",
1734 | "c]": "⦄",
1735 | "Yot": "Ϳ",
1736 | "goal": "⊢",
1737 | "Vdash": "⊩",
1738 | "Vert": "‖",
1739 | "Vvdash": "⊪"
1740 |
1741 | }
1742 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.json' {
2 | const value: any;
3 | export default value;
4 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "lib",
6 | "noImplicitAny": false,
7 | "alwaysStrict": true,
8 | "allowJs": true,
9 | "lib": [ "es6", "dom" ],
10 | "jsx": "react",
11 | "sourceMap": false
12 | },
13 | "include": [ "src/*" ]
14 | }
15 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended"
5 | ],
6 | "jsRules": {},
7 | "rules": {
8 | "quotemark": [true, "single", "avoid-escape"],
9 | "member-access": [true, "no-public"],
10 | "interface-name": [false],
11 | "max-classes-per-file": [false],
12 | "object-literal-sort-keys": false,
13 | "unified-signatures": false,
14 | "no-empty": false,
15 | "no-console": [false]
16 | },
17 | "rulesDirectory": []
18 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 |
6 | const MonacoEditorSrc = path.join(__dirname, 'node_modules', 'react-monaco-editor');
7 | const VSMonacoEditorSrc = path.join(__dirname, 'node_modules', 'monaco-editor', 'min', 'vs');
8 |
9 | let distDir = path.resolve(__dirname, 'dist');
10 |
11 | module.exports = {
12 | mode: 'production',
13 | entry: {
14 | jsx: './src/index.tsx',
15 | // html: './public/index.html',
16 | // vendor: ['react', 'react-dom']
17 | },
18 | output: {
19 | path: distDir,
20 | filename: 'index.js',
21 | publicPath: './',
22 | },
23 | resolve: {
24 | extensions: ['.ts', '.tsx', '.js'],
25 | alias: {'react-monaco-editor': MonacoEditorSrc},
26 | fallback: {
27 | "path": require.resolve("path-browserify"),
28 | "buffer": require.resolve("buffer/")
29 | }
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /webworkerscript\.js$/,
35 | use: { loader:'worker-loader' },
36 | },
37 | {
38 | test: /\.tsx?$/,
39 | use: [
40 | // To use babel in lean-client-js-browser, add the following to this package.json
41 | // "babel-core": "^6.26.3",
42 | // "babel-loader": "^7.1.2",
43 | // "babel-polyfill": "^6.26.0",
44 | // "babel-preset-env": "^1.7.0",
45 | // 'babel-loader?presets[]=env',
46 | 'ts-loader'
47 | ],
48 | },
49 | ],
50 | },
51 | devServer: {
52 | allowedHosts: 'all',
53 | static: [
54 | {
55 | directory: distDir,
56 | publicPath: '/'
57 | },
58 | ]
59 | },
60 | plugins: [
61 | new HtmlWebpackPlugin({
62 | template: 'public/index.html'
63 | }),
64 | new CopyWebpackPlugin({
65 | patterns: [
66 | { from: VSMonacoEditorSrc, to: 'vs', },
67 | { from: 'public/index.css', to: 'index.css', },
68 | { from: 'public/lean_logo.svg', to: 'lean_logo.svg', },
69 | { from: 'public/display-goal-light.svg', to: 'display-goal-light.svg', },
70 | { from: 'public/display-list-light.svg', to: 'display-list-light.svg', },
71 | ]
72 | }),
73 | new webpack.ProvidePlugin({
74 | Buffer: ['buffer', 'Buffer'],
75 | }),
76 | ],
77 | externals: {
78 | // react: 'require("react")',
79 | // 'react-dom': 'require("react-dom")',
80 | },
81 | };
82 |
--------------------------------------------------------------------------------