diff --git a/package.json b/package.json index 1cfd92f..c653cb2 100644 --- a/package.json +++ b/package.json @@ -16,15 +16,15 @@ }, "devDependencies": { "@eslint/js": "^9.26.0", - "@types/react": "^19.1.3", - "@types/react-dom": "^19.1.3", + "@types/react": "^19.1.4", + "@types/react-dom": "^19.1.5", "@vitejs/plugin-react-swc": "^3.9.0", "eslint": "^9.26.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.1.0", "typescript": "~5.8.3", - "typescript-eslint": "^8.32.0", + "typescript-eslint": "^8.32.1", "vite": "^6.3.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1e9c27..658260d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: dependencies: dope2: specifier: git+https://deemz.org/git/joeri/dope2.git - version: git+https://deemz.org/git/joeri/dope2.git#a9e21d7d94a7c75c34a95116931b458507131694 + version: git+https://deemz.org/git/joeri/dope2.git#e631f11aa52b2adda8809c1b0b41cc991fbe8890 react: specifier: ^19.1.0 version: 19.1.0 @@ -22,11 +22,11 @@ importers: specifier: ^9.26.0 version: 9.26.0 '@types/react': - specifier: ^19.1.3 - version: 19.1.3 + specifier: ^19.1.4 + version: 19.1.4 '@types/react-dom': - specifier: ^19.1.3 - version: 19.1.3(@types/react@19.1.3) + specifier: ^19.1.5 + version: 19.1.5(@types/react@19.1.4) '@vitejs/plugin-react-swc': specifier: ^3.9.0 version: 3.9.0(vite@6.3.5) @@ -46,8 +46,8 @@ importers: specifier: ~5.8.3 version: 5.8.3 typescript-eslint: - specifier: ^8.32.0 - version: 8.32.0(eslint@9.26.0)(typescript@5.8.3) + specifier: ^8.32.1 + version: 8.32.1(eslint@9.26.0)(typescript@5.8.3) vite: specifier: ^6.3.5 version: 6.3.5 @@ -262,8 +262,8 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@modelcontextprotocol/sdk@1.11.1': - resolution: {integrity: sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==} + '@modelcontextprotocol/sdk@1.11.2': + resolution: {integrity: sha512-H9vwztj5OAqHg9GockCQC06k1natgcxWQSRpQcPJf6i5+MWBzfKkRtxGbjQf0X2ihii0ffLZCRGbYV2f2bjNCQ==} engines: {node: '>=18'} '@nodelib/fs.scandir@2.1.5': @@ -459,59 +459,59 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/react-dom@19.1.3': - resolution: {integrity: sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==} + '@types/react-dom@19.1.5': + resolution: {integrity: sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg==} peerDependencies: '@types/react': ^19.0.0 - '@types/react@19.1.3': - resolution: {integrity: sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==} + '@types/react@19.1.4': + resolution: {integrity: sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==} - '@typescript-eslint/eslint-plugin@8.32.0': - resolution: {integrity: sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==} + '@typescript-eslint/eslint-plugin@8.32.1': + resolution: {integrity: sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.32.0': - resolution: {integrity: sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==} + '@typescript-eslint/parser@8.32.1': + resolution: {integrity: sha512-LKMrmwCPoLhM45Z00O1ulb6jwyVr2kr3XJp+G+tSEZcbauNnScewcQwtJqXDhXeYPDEjZ8C1SjXm015CirEmGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/scope-manager@8.32.0': - resolution: {integrity: sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==} + '@typescript-eslint/scope-manager@8.32.1': + resolution: {integrity: sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.32.0': - resolution: {integrity: sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==} + '@typescript-eslint/type-utils@8.32.1': + resolution: {integrity: sha512-mv9YpQGA8iIsl5KyUPi+FGLm7+bA4fgXaeRcFKRDRwDMu4iwrSHeDPipwueNXhdIIZltwCJv+NkxftECbIZWfA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/types@8.32.0': - resolution: {integrity: sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==} + '@typescript-eslint/types@8.32.1': + resolution: {integrity: sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.32.0': - resolution: {integrity: sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==} + '@typescript-eslint/typescript-estree@8.32.1': + resolution: {integrity: sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.32.0': - resolution: {integrity: sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==} + '@typescript-eslint/utils@8.32.1': + resolution: {integrity: sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/visitor-keys@8.32.0': - resolution: {integrity: sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==} + '@typescript-eslint/visitor-keys@8.32.1': + resolution: {integrity: sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-react-swc@3.9.0': @@ -633,8 +633,8 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - dope2@git+https://deemz.org/git/joeri/dope2.git#a9e21d7d94a7c75c34a95116931b458507131694: - resolution: {commit: a9e21d7d94a7c75c34a95116931b458507131694, repo: https://deemz.org/git/joeri/dope2.git, type: git} + dope2@git+https://deemz.org/git/joeri/dope2.git#e631f11aa52b2adda8809c1b0b41cc991fbe8890: + resolution: {commit: e631f11aa52b2adda8809c1b0b41cc991fbe8890, repo: https://deemz.org/git/joeri/dope2.git, type: git} version: 0.0.1 dunder-proto@1.0.1: @@ -868,6 +868,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.4: + resolution: {integrity: sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==} + engines: {node: '>= 4'} + import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -1109,8 +1113,8 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} hasBin: true @@ -1191,8 +1195,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typescript-eslint@8.32.0: - resolution: {integrity: sha512-UMq2kxdXCzinFFPsXc9o2ozIpYCCOiEC46MG3yEh5Vipq6BO27otTtEBZA1fQ66DulEUgE97ucQ/3YY66CPg0A==} + typescript-eslint@8.32.1: + resolution: {integrity: sha512-D7el+eaDHAmXvrZBy1zpzSNIRqnCOrkwTgZxTu3MUqRWk8k0q9m9Ho4+vPf7iHtgUfrK/o8IZaEApsxPlHTFCg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -1412,7 +1416,7 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@modelcontextprotocol/sdk@1.11.1': + '@modelcontextprotocol/sdk@1.11.2': dependencies: content-type: 1.0.5 cors: 2.8.5 @@ -1555,52 +1559,52 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/react-dom@19.1.3(@types/react@19.1.3)': + '@types/react-dom@19.1.5(@types/react@19.1.4)': dependencies: - '@types/react': 19.1.3 + '@types/react': 19.1.4 - '@types/react@19.1.3': + '@types/react@19.1.4': dependencies: csstype: 3.1.3 - '@typescript-eslint/eslint-plugin@8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.32.0(eslint@9.26.0)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.32.0 - '@typescript-eslint/type-utils': 8.32.0(eslint@9.26.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.32.0(eslint@9.26.0)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.32.0 + '@typescript-eslint/parser': 8.32.1(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.32.1 + '@typescript-eslint/type-utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.32.1 eslint: 9.26.0 graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 7.0.4 natural-compare: 1.4.0 ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.32.0(eslint@9.26.0)(typescript@5.8.3)': + '@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.32.0 - '@typescript-eslint/types': 8.32.0 - '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.32.0 + '@typescript-eslint/scope-manager': 8.32.1 + '@typescript-eslint/types': 8.32.1 + '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.32.1 debug: 4.4.0 eslint: 9.26.0 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.32.0': + '@typescript-eslint/scope-manager@8.32.1': dependencies: - '@typescript-eslint/types': 8.32.0 - '@typescript-eslint/visitor-keys': 8.32.0 + '@typescript-eslint/types': 8.32.1 + '@typescript-eslint/visitor-keys': 8.32.1 - '@typescript-eslint/type-utils@8.32.0(eslint@9.26.0)(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.32.1(eslint@9.26.0)(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.32.0(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3) debug: 4.4.0 eslint: 9.26.0 ts-api-utils: 2.1.0(typescript@5.8.3) @@ -1608,36 +1612,36 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.32.0': {} + '@typescript-eslint/types@8.32.1': {} - '@typescript-eslint/typescript-estree@8.32.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.32.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.32.0 - '@typescript-eslint/visitor-keys': 8.32.0 + '@typescript-eslint/types': 8.32.1 + '@typescript-eslint/visitor-keys': 8.32.1 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.2 ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.32.0(eslint@9.26.0)(typescript@5.8.3)': + '@typescript-eslint/utils@8.32.1(eslint@9.26.0)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0) - '@typescript-eslint/scope-manager': 8.32.0 - '@typescript-eslint/types': 8.32.0 - '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.32.1 + '@typescript-eslint/types': 8.32.1 + '@typescript-eslint/typescript-estree': 8.32.1(typescript@5.8.3) eslint: 9.26.0 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.32.0': + '@typescript-eslint/visitor-keys@8.32.1': dependencies: - '@typescript-eslint/types': 8.32.0 + '@typescript-eslint/types': 8.32.1 eslint-visitor-keys: 4.2.0 '@vitejs/plugin-react-swc@3.9.0(vite@6.3.5)': @@ -1758,7 +1762,7 @@ snapshots: depd@2.0.0: {} - dope2@git+https://deemz.org/git/joeri/dope2.git#a9e21d7d94a7c75c34a95116931b458507131694: + dope2@git+https://deemz.org/git/joeri/dope2.git#e631f11aa52b2adda8809c1b0b41cc991fbe8890: dependencies: functional-red-black-tree: 1.0.1 @@ -1842,7 +1846,7 @@ snapshots: '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@modelcontextprotocol/sdk': 1.11.1 + '@modelcontextprotocol/sdk': 1.11.2 '@types/estree': 1.0.7 '@types/json-schema': 7.0.15 ajv: 6.12.6 @@ -2053,6 +2057,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.4: {} + import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -2272,7 +2278,7 @@ snapshots: scheduler@0.26.0: {} - semver@7.7.1: {} + semver@7.7.2: {} send@1.2.0: dependencies: @@ -2370,11 +2376,11 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.1 - typescript-eslint@8.32.0(eslint@9.26.0)(typescript@5.8.3): + typescript-eslint@8.32.1(eslint@9.26.0)(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3) - '@typescript-eslint/parser': 8.32.0(eslint@9.26.0)(typescript@5.8.3) - '@typescript-eslint/utils': 8.32.0(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.26.0)(typescript@5.8.3))(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/parser': 8.32.1(eslint@9.26.0)(typescript@5.8.3) + '@typescript-eslint/utils': 8.32.1(eslint@9.26.0)(typescript@5.8.3) eslint: 9.26.0 typescript: 5.8.3 transitivePeerDependencies: diff --git a/src/App.css b/src/App.css index f609a8f..ebc51c9 100644 --- a/src/App.css +++ b/src/App.css @@ -42,3 +42,15 @@ footer { footer a { color: white; } + +.command { + border: 1px solid black; + border-radius: 5px; + padding: 0 6px 0 6px; + margin: 0 4px 0 4px; +} + +.command.highlighted { + background-color: dodgerblue; + color: white; +} diff --git a/src/App.tsx b/src/App.tsx index a93ebde..5f4087a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import './App.css' import { Editor, type EditorState } from './Editor' import { initialEditorState, nonEmptyEditorState, tripleFunctionCallEditorState } from "./configurations"; import { CommandContext } from './CommandContext'; +import { EnvContext } from './EnvContext'; export function App() { const [history, setHistory] = useState([initialEditorState]); @@ -52,7 +53,7 @@ export function App() { const commands = [ ["call" , "[c] call" ], ["eval" , "[u] [Tab] [Enter] eval"], - ["transform", "[t] transform" ], + ["transform", "[t] [.] transform" ], ["let" , "[=] let ... in ..." ], ]; @@ -80,15 +81,14 @@ export function App() { } -
- +
+ {}} onCancel={() => {}} filter={() => true} - focus={true} />
diff --git a/src/CallBlock.tsx b/src/CallBlock.tsx index d8814c7..19160dd 100644 --- a/src/CallBlock.tsx +++ b/src/CallBlock.tsx @@ -13,7 +13,6 @@ export interface CallBlockState< InputState=EditorState, > { kind: "call"; - env: any; fn: FnState; input: InputState; resolved: undefined | Dynamic; @@ -28,7 +27,7 @@ interface CallBlockProps< } function headlessCallBlock({state, setState, onResolve}: CallBlockProps) { - const [unifyError, setUnifyError] = useState(undefined); + const [unifyError, setUnifyError] = useState(undefined); const {fn, input } = state; const setFn = (fn: EditorState) => { setState({...state, fn}); @@ -52,7 +51,7 @@ function headlessCallBlock({state, setState, onResolve}: CallBlockProps) { if (!(e instanceof UnifyError)) { throw e; } - setUnifyError(e); + setUnifyError(e as typeof UnifyError); onResolve({ ...state, resolved: undefined }) diff --git a/src/CommandContext.ts b/src/CommandContext.ts index 8695ee5..58bca8f 100644 --- a/src/CommandContext.ts +++ b/src/CommandContext.ts @@ -1,3 +1,9 @@ import { createContext } from "react"; -export const CommandContext = createContext<{[key:string]: () => void}>({}); +interface GlobalActions { + undo: () => void; + redo: () => void; + doHighlight: {[key:string]: () => void}; +} + +export const CommandContext = createContext(null); diff --git a/src/Editor.css b/src/Editor.css index 9395c41..723a530 100644 --- a/src/Editor.css +++ b/src/Editor.css @@ -2,6 +2,6 @@ display: inline-block; } -.command { +.commandInput { width: 160px; } \ No newline at end of file diff --git a/src/Editor.tsx b/src/Editor.tsx index 5b4ff30..7c3a829 100644 --- a/src/Editor.tsx +++ b/src/Editor.tsx @@ -26,7 +26,7 @@ interface EditorProps extends State2Props { } function getCommands(type) { - const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=']; + const commands = ['u', 't', 'Enter', 'Backspace', 'ArrowLeft', 'ArrowRight', 'Tab', 'l', '=', '.']; if (getSymbol(type) === symbolFunction) { commands.push('c'); } @@ -34,9 +34,9 @@ function getCommands(type) { } function getShortCommands(type) { if (getSymbol(type) === symbolFunction) { - return 'c|Tab|t'; + return 'c|Tab|.'; } - return 'Tab|t'; + return 'Tab|.'; } function removeFocus(state: EditorState): EditorState { @@ -72,7 +72,7 @@ export function Editor({state, setState, onResolve, onCancel, filter}: EditorPro } } - const doHighlight = useContext(CommandContext); + const globalContext = useContext(CommandContext); const onCommand = (e: React.KeyboardEvent) => { const type = getType(state.resolved); const commands = getCommands(type); @@ -84,7 +84,7 @@ export function Editor({state, setState, onResolve, onCancel, filter}: EditorPro // u -> pass Up if (e.key === "u" || e.key === "Enter" || e.key === "Tab" && !e.shiftKey) { onResolve(state); - doHighlight.eval(); + globalContext?.doHighlight.eval(); return; } if (e.key === "Tab" && e.shiftKey) { @@ -96,26 +96,24 @@ export function Editor({state, setState, onResolve, onCancel, filter}: EditorPro // we become CallBlock setState({ kind: "call", - env: state.env, fn: removeFocus(state), input: initialEditorState, resolved: undefined, }); - doHighlight.call(); + globalContext?.doHighlight.call(); // focusNextElement(); return; } // t -> Transform - if (e.key === "t") { + if (e.key === "t" || e.key === ".") { // we become CallBlock setState({ kind: "call", - env: state.env, fn: initialEditorState, input: removeFocus(state), resolved: undefined, }); - doHighlight.transform(); + globalContext?.doHighlight.transform(); return; } if (e.key === "Backspace" || e.key === "ArrowLeft") { @@ -132,13 +130,12 @@ export function Editor({state, setState, onResolve, onCancel, filter}: EditorPro // we become LetInBlock setState({ kind: "let", - env: state.env, - inner: initialEditorState, + inner: removeFocus(initialEditorState), name: "", - value: state, + value: removeFocus(state), resolved: undefined, }); - doHighlight.let(); + globalContext?.doHighlight.let(); return; } }; @@ -179,7 +176,7 @@ export function Editor({state, setState, onResolve, onCancel, filter}: EditorPro ? `} onKeyDown={onCommand} value={""} diff --git a/src/EnvContext.ts b/src/EnvContext.ts new file mode 100644 index 0000000..bb9e0e7 --- /dev/null +++ b/src/EnvContext.ts @@ -0,0 +1,9 @@ +import { createContext } from "react"; +import { getDefaultTypeParser, module2Env, ModuleStd } from "dope2"; + +const mkType = getDefaultTypeParser(); +export const extendedEnv = module2Env(ModuleStd.concat([ + ["functionWith3Params", { i: i => j => k => i + j + k, t: mkType("Int->Int->Int->Int") }], +])); + +export const EnvContext = createContext(extendedEnv); \ No newline at end of file diff --git a/src/InputBlock.tsx b/src/InputBlock.tsx index c070d3e..67c1c7a 100644 --- a/src/InputBlock.tsx +++ b/src/InputBlock.tsx @@ -1,16 +1,16 @@ import { Double, getType, Int, newDynamic, trie } from "dope2"; -import { focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery"; +import { autoInputWidth, focusNextElement, focusPrevElement, setRightMostCaretPosition } from "./util/dom_trickery"; import { parseDouble, parseInt } from "./util/parse"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; import { Type } from "./Type"; import "./InputBlock.css"; import type { Dynamic, State2Props } from "./util/extra"; +import { EnvContext } from "./EnvContext"; export interface InputBlockState { kind: "input"; - env: any; text: string; resolved: undefined | Dynamic; focus: boolean @@ -39,7 +39,8 @@ const computeSuggestions = (text, env, filter) => { } export function InputBlock({ state, setState, filter, onResolve, onCancel }: InputBlockProps) { - const {env, text, resolved, focus} = state; + const {text, resolved, focus} = state; + const env = useContext(EnvContext); const inputRef = useRef(null); const [i, setI] = useState(0); // selected suggestion idx const [haveFocus, setHaveFocus] = useState(false); // whether to render suggestions or not @@ -51,12 +52,7 @@ export function InputBlock({ state, setState, filter, onResolve, onCancel }: Inp const singleSuggestion = trie.growPrefix(env.name2dyn)(text); const suggestions = useMemo(() => computeSuggestions(text, env, filter), [text]); - useEffect(() => { - setI(0); // reset - if (inputRef.current) { - inputRef.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`; - } - }, [text]); + useEffect(() => autoInputWidth(inputRef, text), [inputRef, text]); useEffect(() => { if (focus) { @@ -67,7 +63,6 @@ export function InputBlock({ state, setState, filter, onResolve, onCancel }: Inp const onSelectSuggestion = ([name, dynamic]) => { onResolve({ kind: "input", - env, text: name, resolved: dynamic, focus: false, @@ -80,7 +75,6 @@ export function InputBlock({ state, setState, filter, onResolve, onCancel }: Inp // un-resolve onResolve({ kind: "input", - env, text: e.target.value, resolved: undefined, focus: true, diff --git a/src/LetInBlock.tsx b/src/LetInBlock.tsx index 07afc38..f88f2f9 100644 --- a/src/LetInBlock.tsx +++ b/src/LetInBlock.tsx @@ -1,10 +1,12 @@ +import { useContext, useEffect, useRef } from "react"; import { Editor, type EditorState } from "./Editor"; +import { EnvContext } from "./EnvContext"; import type { Dynamic, State2Props } from "./util/extra"; - +import { growEnv } from "dope2"; +import { autoInputWidth } from "./util/dom_trickery"; export interface LetInBlockState { kind: "let"; - env: any; name: string; value: EditorState; inner: EditorState; @@ -17,13 +19,26 @@ interface LetInBlockProps extends State2Props { export function LetInBlock({state, setState, onResolve}: LetInBlockProps) { - const {env, name, value, inner, resolved} = state; + const {name, value, inner} = state; + const env = useContext(EnvContext); + const nameRef = useRef(null); + const onChangeName = (e: React.ChangeEvent) => { setState({...state, name: e.target.value}); } + + useEffect(() => { + nameRef.current?.focus(); + }, []); + + useEffect(() => autoInputWidth(nameRef, name), [nameRef, name]); + + const innerEnv = (name !== '') && value.resolved + && growEnv(env)(name)(value.resolved) || env; return
let {} } setState={(state: EditorState) => {} } /> - in +  in
- true} - onResolve={(state: EditorState) => {} } - onCancel={() => {} } - setState={(state: EditorState) => {} } - /> + + setState({...state, inner})} + filter={() => true} + onResolve={onResolve} + onCancel={() => {}} + /> +
} diff --git a/src/configurations.ts b/src/configurations.ts index 20bd600..fc00221 100644 --- a/src/configurations.ts +++ b/src/configurations.ts @@ -1,39 +1,30 @@ -import { getDefaultTypeParser, module2Env, ModuleStd, trie, Int, apply } from "dope2"; +import { apply, Int, trie } from "dope2"; import type { EditorState } from "./Editor"; - -const mkType = getDefaultTypeParser(); -export const extendedEnv = module2Env(ModuleStd.concat([ - ["functionWith3Params", { i: i => j => k => i + j + k, t: mkType("Int->Int->Int->Int") }], -])); +import { extendedEnv } from "./EnvContext"; export const initialEditorState: EditorState = { kind: "input", - env: extendedEnv, text: "", resolved: undefined, focus: true, }; -const listPush = trie.get(initialEditorState.env.name2dyn)("list.push"); -const listEmptyList = trie.get(initialEditorState.env.name2dyn)("list.emptyList"); +const listPush = trie.get(extendedEnv.name2dyn)("list.push"); +const listEmptyList = trie.get(extendedEnv.name2dyn)("list.emptyList"); const fourtyTwo = { i: 42n, t: Int }; export const nonEmptyEditorState: EditorState = { kind: "call", - env: initialEditorState.env, fn: { kind: "call", - env: initialEditorState.env, fn: { kind: "input", - env: initialEditorState.env, text: "list.push", resolved: listPush, focus: false, }, input: { kind: "input", - env: initialEditorState.env, text: "list.emptyList", resolved: listEmptyList, focus: false, @@ -43,7 +34,6 @@ export const nonEmptyEditorState: EditorState = { }, input: { kind: "input", - env: initialEditorState.env, text: "42", resolved: fourtyTwo, focus: false, @@ -52,29 +42,24 @@ export const nonEmptyEditorState: EditorState = { resolved: undefined, }; -const functionWith3Params = trie.get(initialEditorState.env.name2dyn)("functionWith3Params"); +const functionWith3Params = trie.get(extendedEnv.name2dyn)("functionWith3Params"); const fourtyThree = { i: 43n, t: Int }; const fourtyFour = { i: 44n, t: Int }; export const tripleFunctionCallEditorState: EditorState = { kind: "call", - env: initialEditorState.env, fn: { kind: "call", - env: initialEditorState.env, fn: { kind: "call", - env: initialEditorState.env, fn: { kind: "input", - env: initialEditorState.env, text: "functionWith3Params", resolved: functionWith3Params, focus: false, }, input: { kind: "input", - env: initialEditorState.env, text: "42", resolved: fourtyTwo, focus: false, @@ -83,7 +68,6 @@ export const tripleFunctionCallEditorState: EditorState = { }, input: { kind: "input", - env: initialEditorState.env, text: "43", resolved: fourtyThree, focus: false, @@ -92,7 +76,6 @@ export const tripleFunctionCallEditorState: EditorState = { }, input: { kind: "input", - env: initialEditorState.env, text: "44", resolved: fourtyFour, focus: false, diff --git a/src/util/dom_trickery.ts b/src/util/dom_trickery.ts index 2ccc419..ffe3e14 100644 --- a/src/util/dom_trickery.ts +++ b/src/util/dom_trickery.ts @@ -1,3 +1,4 @@ +import type { Ref } from "react"; // If there is a caret anywhere in the document (user entering text), returns the position of the caret in the focused element export function getCaretPosition(): number | undefined { @@ -46,4 +47,10 @@ export function focusPrevElement() { prevElem.focus(); setRightMostCaretPosition(prevElem); } -} \ No newline at end of file +} + +export const autoInputWidth = (inputRef: React.RefObject, text) => { + if (inputRef.current) { + inputRef.current.style.width = `${text.length === 0 ? 140 : (text.length*8.7)}px`; + } +}