├── .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 | 279 |
280 | 282 |
283 | 285 |
286 | 287 | {/* */} 288 |
289 | 290 | 291 |
292 |   293 | Live in-browser version of the 294 | Lean theorem prover 295 | 296 | . 297 |
298 | {this.props.status && 299 | ( 300 | Could not fetch (error: {this.props.status})!  301 | {this.props.status.startsWith('TypeError') && ( 302 | If you see 303 | cross-origin (CORS) errors 304 | in your browser's dev console, try 305 | a CORS proxy 306 | , e.g. prepend https://cors-anywhere.herokuapp.com/ to the beginning of your URL. 307 | )} 308 | )} 309 |
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 |
347 | Load .lean from  348 | URL:  349 | 350 |
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(
414 | {k} : {commit} 415 |
); 416 | } else { 417 | libinfo.push(
419 | {k} : {v} 420 |
); 421 | } 422 | } 423 | } 424 | } 425 | 426 | return createPortal( 427 | , 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 |
725 | 728 |
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 | --------------------------------------------------------------------------------