diff --git a/package.json b/package.json index b737dc7..3c7602f 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,25 @@ "tauri": "tauri" }, "dependencies": { + "@codemirror/commands": "^6.3.3", + "@codemirror/lang-javascript": "^6.2.1", + "@codemirror/lang-markdown": "^6.2.3", + "@codemirror/lang-python": "^6.1.3", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.23.0", + "@lezer/highlight": "^1.2.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.0.2", "@tauri-apps/api": "^1.5.2", + "@uiw/react-codemirror": "^4.21.21", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "codemirror": "^6.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^2.2.0", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "thememirror": "^2.0.1" }, "devDependencies": { "@tauri-apps/cli": "^1.5.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1c5cbb..200e264 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,27 @@ settings: excludeLinksFromLockfile: false dependencies: + '@codemirror/commands': + specifier: ^6.3.3 + version: 6.3.3 + '@codemirror/lang-javascript': + specifier: ^6.2.1 + version: 6.2.1 + '@codemirror/lang-markdown': + specifier: ^6.2.3 + version: 6.2.3 + '@codemirror/lang-python': + specifier: ^6.1.3 + version: 6.1.3(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/state': + specifier: ^6.4.0 + version: 6.4.0 + '@codemirror/view': + specifier: ^6.23.0 + version: 6.23.0 + '@lezer/highlight': + specifier: ^1.2.0 + version: 1.2.0 '@radix-ui/react-icons': specifier: ^1.3.0 version: 1.3.0(react@18.2.0) @@ -14,12 +35,18 @@ dependencies: '@tauri-apps/api': specifier: ^1.5.2 version: 1.5.2 + '@uiw/react-codemirror': + specifier: ^4.21.21 + version: 4.21.21(@babel/runtime@7.23.8)(@codemirror/autocomplete@6.12.0)(@codemirror/language@6.10.0)(@codemirror/lint@6.4.2)(@codemirror/search@6.5.5)(@codemirror/state@6.4.0)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.23.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 clsx: specifier: ^2.1.0 version: 2.1.0 + codemirror: + specifier: ^6.0.1 + version: 6.0.1(@lezer/common@1.2.0) react: specifier: ^18.2.0 version: 18.2.0 @@ -32,6 +59,9 @@ dependencies: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.1) + thememirror: + specifier: ^2.0.1 + version: 2.0.1(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0) devDependencies: '@tauri-apps/cli': @@ -302,6 +332,139 @@ packages: to-fast-properties: 2.0.0 dev: true + /@codemirror/autocomplete@6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0): + resolution: {integrity: sha512-r4IjdYFthwbCQyvqnSlx0WBHRHi8nBvU+WjJxFUij81qsBfhNudf/XKKmmC2j3m0LaOYUQTf3qiEK1J8lO1sdg==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 + dependencies: + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + dev: false + + /@codemirror/commands@6.3.3: + resolution: {integrity: sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==} + dependencies: + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + dev: false + + /@codemirror/lang-css@6.2.1(@codemirror/view@6.23.0): + resolution: {integrity: sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@lezer/common': 1.2.0 + '@lezer/css': 1.1.7 + transitivePeerDependencies: + - '@codemirror/view' + dev: false + + /@codemirror/lang-html@6.4.7: + resolution: {integrity: sha512-y9hWSSO41XlcL4uYwWyk0lEgTHcelWWfRuqmvcAmxfCs0HNWZdriWo/EU43S63SxEZpc1Hd50Itw7ktfQvfkUg==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/lang-css': 6.2.1(@codemirror/view@6.23.0) + '@codemirror/lang-javascript': 6.2.1 + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + '@lezer/css': 1.1.7 + '@lezer/html': 1.3.8 + dev: false + + /@codemirror/lang-javascript@6.2.1: + resolution: {integrity: sha512-jlFOXTejVyiQCW3EQwvKH0m99bUYIw40oPmFjSX2VS78yzfe0HELZ+NEo9Yfo1MkGRpGlj3Gnu4rdxV1EnAs5A==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/language': 6.10.0 + '@codemirror/lint': 6.4.2 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + '@lezer/javascript': 1.4.12 + dev: false + + /@codemirror/lang-markdown@6.2.3: + resolution: {integrity: sha512-wCewRLWpdefWi7uVkHIDiE8+45Fe4buvMDZkihqEom5uRUQrl76Zb13emjeK3W+8pcRgRfAmwelURBbxNEKCIg==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/lang-html': 6.4.7 + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + '@lezer/markdown': 1.2.0 + dev: false + + /@codemirror/lang-python@6.1.3(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0): + resolution: {integrity: sha512-S9w2Jl74hFlD5nqtUMIaXAq9t5WlM0acCkyuQWUUSvZclk1sV+UfnpFiZzuZSG+hfEaOmxKR5UxY/Uxswn7EhQ==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/language': 6.10.0 + '@lezer/python': 1.1.10 + transitivePeerDependencies: + - '@codemirror/state' + - '@codemirror/view' + - '@lezer/common' + dev: false + + /@codemirror/language@6.10.0: + resolution: {integrity: sha512-2vaNn9aPGCRFKWcHPFksctzJ8yS5p7YoaT+jHpc0UGKzNuAIx4qy6R5wiqbP+heEEdyaABA582mNqSHzSoYdmg==} + dependencies: + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/common': 1.2.0 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.3.14 + style-mod: 4.1.0 + dev: false + + /@codemirror/lint@6.4.2: + resolution: {integrity: sha512-wzRkluWb1ptPKdzlsrbwwjYCPLgzU6N88YBAmlZi8WFyuiEduSd05MnJYNogzyc8rPK7pj6m95ptUApc8sHKVA==} + dependencies: + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + crelt: 1.0.6 + dev: false + + /@codemirror/search@6.5.5: + resolution: {integrity: sha512-PIEN3Ke1buPod2EHbJsoQwlbpkz30qGZKcnmH1eihq9+bPQx8gelauUwLYaY4vBOuBAuEhmpDLii4rj/uO0yMA==} + dependencies: + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + crelt: 1.0.6 + dev: false + + /@codemirror/state@6.4.0: + resolution: {integrity: sha512-hm8XshYj5Fo30Bb922QX9hXB/bxOAVH+qaqHBzw5TKa72vOeslyGwd4X8M0c1dJ9JqxlaMceOQ8RsL9tC7gU0A==} + dev: false + + /@codemirror/theme-one-dark@6.1.2: + resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} + dependencies: + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + '@lezer/highlight': 1.2.0 + dev: false + + /@codemirror/view@6.23.0: + resolution: {integrity: sha512-/51px9N4uW8NpuWkyUX+iam5+PM6io2fm+QmRnzwqBy5v/pwGg9T0kILFtYeum8hjuvENtgsGNKluOfqIICmeQ==} + dependencies: + '@codemirror/state': 6.4.0 + style-mod: 4.1.0 + w3c-keyname: 2.2.8 + dev: false + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -536,6 +699,60 @@ packages: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 + /@lezer/common@1.2.0: + resolution: {integrity: sha512-Wmvlm4q6tRpwiy20TnB3yyLTZim38Tkc50dPY8biQRwqE+ati/wD84rm3N15hikvdT4uSg9phs9ubjvcLmkpKg==} + dev: false + + /@lezer/css@1.1.7: + resolution: {integrity: sha512-7BlFFAKNn/b39jJLrhdLSX5A2k56GIJvyLqdmm7UU+7XvequY084iuKDMAEhAmAzHnwDE8FK4OQtsIUssW91tg==} + dependencies: + '@lezer/common': 1.2.0 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.3.14 + dev: false + + /@lezer/highlight@1.2.0: + resolution: {integrity: sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==} + dependencies: + '@lezer/common': 1.2.0 + dev: false + + /@lezer/html@1.3.8: + resolution: {integrity: sha512-EXseJ3pUzWxE6XQBQdqWHZqqlGQRSuNMBcLb6mZWS2J2v+QZhOObD+3ZIKIcm59ntTzyor4LqFTb72iJc3k23Q==} + dependencies: + '@lezer/common': 1.2.0 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.3.14 + dev: false + + /@lezer/javascript@1.4.12: + resolution: {integrity: sha512-kwO5MftUiyfKBcECMEDc4HYnc10JME9kTJNPVoCXqJj/Y+ASWF0rgstORi3BThlQI6SoPSshrK5TjuiLFnr29A==} + dependencies: + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.3.14 + dev: false + + /@lezer/lr@1.3.14: + resolution: {integrity: sha512-z5mY4LStlA3yL7aHT/rqgG614cfcvklS+8oFRFBYrs4YaWLJyKKM4+nN6KopToX0o9Hj6zmH6M5kinOYuy06ug==} + dependencies: + '@lezer/common': 1.2.0 + dev: false + + /@lezer/markdown@1.2.0: + resolution: {integrity: sha512-d7MwsfAukZJo1GpPrcPGa3MxaFFOqNp0gbqF+3F7pTeNDOgeJN1muXzx1XXDPt+Ac+/voCzsH7qXqnn+xReG/g==} + dependencies: + '@lezer/common': 1.2.0 + '@lezer/highlight': 1.2.0 + dev: false + + /@lezer/python@1.1.10: + resolution: {integrity: sha512-pvSjn+OWivmA/si/SFeGouHO50xoOZcPIFzf8dql0gRvcfCvLDpVIpnnGFFlB7wa0WDscDLo0NmH+4Tx80nBdQ==} + dependencies: + '@lezer/common': 1.2.0 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.3.14 + dev: false + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -763,6 +980,53 @@ packages: /@types/scheduler@0.16.8: resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + /@uiw/codemirror-extensions-basic-setup@4.21.21(@codemirror/autocomplete@6.12.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.0)(@codemirror/lint@6.4.2)(@codemirror/search@6.5.5)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0): + resolution: {integrity: sha512-+0i9dPrRSa8Mf0CvyrMvnAhajnqwsP3IMRRlaHDRgsSGL8igc4z7MhvUPn+7cWFAAqWzQRhMdMSWzo6/TEa3EA==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/commands': 6.3.3 + '@codemirror/language': 6.10.0 + '@codemirror/lint': 6.4.2 + '@codemirror/search': 6.5.5 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + dev: false + + /@uiw/react-codemirror@4.21.21(@babel/runtime@7.23.8)(@codemirror/autocomplete@6.12.0)(@codemirror/language@6.10.0)(@codemirror/lint@6.4.2)(@codemirror/search@6.5.5)(@codemirror/state@6.4.0)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.23.0)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-PaxBMarufMWoR0qc5zuvBSt76rJ9POm9qoOaJbqRmnNL2viaF+d+Paf2blPSlm1JSnqn7hlRjio+40nZJ9TKzw==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.23.8 + '@codemirror/commands': 6.3.3 + '@codemirror/state': 6.4.0 + '@codemirror/theme-one-dark': 6.1.2 + '@codemirror/view': 6.23.0 + '@uiw/codemirror-extensions-basic-setup': 4.21.21(@codemirror/autocomplete@6.12.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.0)(@codemirror/lint@6.4.2)(@codemirror/search@6.5.5)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0) + codemirror: 6.0.1(@lezer/common@1.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + dev: false + /@vitejs/plugin-react@4.2.1(vite@4.5.1): resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -909,6 +1173,20 @@ packages: engines: {node: '>=6'} dev: false + /codemirror@6.0.1(@lezer/common@1.2.0): + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + dependencies: + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0)(@lezer/common@1.2.0) + '@codemirror/commands': 6.3.3 + '@codemirror/language': 6.10.0 + '@codemirror/lint': 6.4.2 + '@codemirror/search': 6.5.5 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + transitivePeerDependencies: + - '@lezer/common' + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -936,6 +1214,10 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1468,6 +1750,10 @@ packages: dependencies: ansi-regex: 6.0.1 + /style-mod@4.1.0: + resolution: {integrity: sha512-Ca5ib8HrFn+f+0n4N4ScTIA9iTOQ7MaGS1ylHcoVqW9J7w2w8PzN6g9gKmTYgGEBH8e120+RCmhpje6jC5uGWA==} + dev: false + /sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1536,6 +1822,18 @@ packages: transitivePeerDependencies: - ts-node + /thememirror@2.0.1(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.0): + resolution: {integrity: sha512-d5i6FVvWWPkwrm4cHLI3t9AT1OrkAt7Ig8dtdYSofgF7C/eiyNuq6zQzSTusWTde3jpW9WLvA9J/fzNKMUsd0w==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + dependencies: + '@codemirror/language': 6.10.0 + '@codemirror/state': 6.4.0 + '@codemirror/view': 6.23.0 + dev: false + /thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1621,6 +1919,10 @@ packages: fsevents: 2.3.3 dev: true + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + dev: false + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} diff --git a/src/App.tsx b/src/App.tsx index 7423681..5b3ba12 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,8 +7,8 @@ function App() { const { cells, createCell } = useNotebook(); return (
- {cells.map((cellID: string) => ( - + {cells.map((cellId: string) => ( + ))}
diff --git a/src/codemirror-themes.ts b/src/codemirror-themes.ts new file mode 100644 index 0000000..22933e4 --- /dev/null +++ b/src/codemirror-themes.ts @@ -0,0 +1,140 @@ +import { createTheme } from "thememirror"; +import { tags as t } from "@lezer/highlight"; + +// Based on Ayu Light and ThemeMirror +export const lightTheme = createTheme({ + variant: "light", + settings: { + background: "#FFFFFF", // '#fcfcfc', + foreground: "#0F172A", //'#5c6166', + caret: "#ffaa33", + selection: "#F1F5F9", //'#036dd626', + gutterBackground: "#FFFFFF", //'#fcfcfc', + gutterForeground: "#8a919966", + lineHighlight: "#8a91991a", + }, + styles: [ + { + tag: t.comment, + color: "#787b8099", + }, + { + tag: t.string, + color: "#86b300", + }, + { + tag: t.regexp, + color: "#4cbf99", + }, + { + tag: [t.number, t.bool, t.null], + color: "#ffaa33", + }, + { + tag: t.variableName, + color: "#5c6166", + }, + { + tag: [t.definitionKeyword, t.modifier], + color: "#fa8d3e", + }, + { + tag: [t.keyword, t.special(t.brace)], + color: "#fa8d3e", + }, + { + tag: t.operator, + color: "#ed9366", + }, + { + tag: t.separator, + color: "#5c6166b3", + }, + { + tag: t.punctuation, + color: "#5c6166", + }, + { + tag: [t.definition(t.propertyName), t.function(t.variableName)], + color: "#f2ae49", + }, + { + tag: [t.className, t.definition(t.typeName)], + color: "#22a4e6", + }, + { + tag: [t.tagName, t.typeName, t.self, t.labelName], + color: "#55b4d4", + }, + { + tag: t.angleBracket, + color: "#55b4d480", + }, + { + tag: t.attributeName, + color: "#f2ae49", + }, + ], +}); + +// Based on Cool Glow and ThemeMirror +export const darkTheme = createTheme({ + variant: "dark", + settings: { + background: "#030711", //'#060521', + foreground: "#E1E7EF", //'#E0E0E0', + caret: "#FFFFFFA6", + selection: "#0F1629", //'#122BBB', + gutterBackground: "#030711", //'#060521', + gutterForeground: "#E1E7EF90", + lineHighlight: "#FFFFFF0F", + }, + styles: [ + { + tag: t.comment, + color: "#AEAEAE", + }, + { + tag: [t.string, t.special(t.brace), t.regexp], + color: "#8DFF8E", + }, + { + tag: [ + t.className, + t.definition(t.propertyName), + t.function(t.variableName), + t.function(t.definition(t.variableName)), + t.definition(t.typeName), + ], + color: "#A3EBFF", + }, + { + tag: [t.number, t.bool, t.null], + color: "#62E9BD", + }, + { + tag: [t.keyword, t.operator], + color: "#2BF1DC", + }, + { + tag: [t.definitionKeyword, t.modifier], + color: "#F8FBB1", + }, + { + tag: [t.variableName, t.self], + color: "#B683CA", + }, + { + tag: [t.angleBracket, t.tagName, t.typeName, t.propertyName], + color: "#60A4F1", + }, + { + tag: t.derefOperator, + color: "#E0E0E0", + }, + { + tag: t.attributeName, + color: "#7BACCA", + }, + ], +}); diff --git a/src/components/Cell.tsx b/src/components/Cell.tsx index 586aee9..4d7921e 100644 --- a/src/components/Cell.tsx +++ b/src/components/Cell.tsx @@ -1,14 +1,14 @@ -import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { useCell } from "../hooks/useCell"; +import { useCell } from "@/hooks/useCell"; +import { Editor } from "@/components//Editor"; -const Cell = ({ cellID }: { cellID: string }) => { - const { content, executeCell, updateCell, cellState, executionCount } = useCell(cellID); +const Cell = ({ cellId }: { cellId: string }) => { + const { executeCell, cellState, executionCount } = useCell(cellId); let actionIcon = "▶"; - switch(cellState) { + switch (cellState) { case "idle": // Let them try again case "errored": @@ -25,21 +25,13 @@ const Cell = ({ cellID }: { cellID: string }) => {
- updateCell(e.target.value)} - /> +
); }; diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx new file mode 100644 index 0000000..ec6e8f4 --- /dev/null +++ b/src/components/Editor.tsx @@ -0,0 +1,120 @@ +import {useCodeMirror} from "@uiw/react-codemirror"; +import { javascript } from "@codemirror/lang-javascript"; +import { python } from "@codemirror/lang-python"; +import { markdown } from "@codemirror/lang-markdown"; +import { lightTheme } from "@/codemirror-themes"; + +import { StateField } from "@codemirror/state"; +import { EditorView, keymap } from "@codemirror/view"; +import { useCell } from "@/hooks/useCell"; + +import { invoke } from "@tauri-apps/api/tauri"; + +import { useRef, useEffect, useMemo } from "react"; + +import { cn } from "@/lib/utils" + +const styleTheme = EditorView.baseTheme({ + "&.cm-editor.cm-focused": { + // This combo here is Tailwind's focus-visible:ring-1, focus-visible:ring-ring, focus-visible:outline-none + outline: "2px solid transparent", + outlineOffset: "2px", + boxShadow: "var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color), var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) hsl(var(--ring)), var(--tw-shadow, 0 0 #0000)", + }, + "&.cm-editor": { + + }, + ".cm-content": { + "padding": "0", + // From Tailwind's text-sm. Not sure if this is actually applying it. + "font-size": "0.875rem", + "line-height": "1.25rem", + } +}); + +const cellIdState = StateField.define({ + create: () => "", + update: (cellId) => { + return cellId; + }, +}); + +export const executeKeybinding = { + key: "Ctrl-Enter", + run: (view: EditorView) => { + const cellId = view.state.field(cellIdState); + invoke("execute_cell", { cellId }); + return true; + }, + preventDefault: true, +}; + +// TODO(Kyle): This should also change the focused cell +export const executeAndGoDownKeybinding = { + key: "Shift-Enter", + run: (view: EditorView) => { + const cellId = view.state.field(cellIdState); + invoke("execute_cell", { cellId }); + return true; + }, + preventDefault: true, +}; + +const baseExtensions = [ + styleTheme, + lightTheme, + keymap.of([executeKeybinding]), + keymap.of([executeAndGoDownKeybinding]) +]; + +export const Editor = ({ cellId, className, language }: { cellId: string, className?: string, language: string }) => { + const ref = useRef(null); + + const { content, updateCell } = useCell(cellId); + + // We need to compute a derived extensions state based on the language of the editor + const extensions = useMemo(() => { + let extensions = [...baseExtensions, cellIdState.init(() => cellId)]; + + switch (language) { + case "javascript": + extensions.push(javascript()); + break; + case "python": + extensions.push(python()); + break; + case "markdown": + extensions.push(markdown()); + break; + default: + break; + } + return extensions; + }, [language, cellId]); + + // NOTE(Kyle): In the long run I'd like to have the cell update and synchronization rely on CodeMirror collaboration, + // transactions and all. For now we can just use the onChange callback to update the cell content. + + const { setContainer } = useCodeMirror({ + container: ref.current, + extensions, + width: "100%", + value: content, + onChange: updateCell, + indentWithTab: false, + }); + + const classes = cn( + "flex w-full h-full bg-transparent", + className + ) + + useEffect(() => { + if (ref.current) { + setContainer(ref.current); + } + }, [ref.current]); + + return
; + +} diff --git a/src/index.css b/src/index.css index 0b46ea1..d00b17f 100644 --- a/src/index.css +++ b/src/index.css @@ -73,4 +73,8 @@ body { @apply bg-background text-foreground; } +} + +.cm-focused-ring { + @apply focus:ring focus:ring-ring; } \ No newline at end of file