├── .gitignore ├── icon.png ├── typst-sympy-calculator.gif ├── .vscodeignore ├── .vscode ├── extensions.json └── launch.json ├── jsconfig.json ├── test ├── suite │ ├── extension.test.js │ └── index.js └── runTest.js ├── .eslintrc.json ├── LICENSE ├── server.py ├── package.json ├── README.md └── extension.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode-test/ 3 | *.vsix 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/vscode-typst-sympy-calculator/HEAD/icon.png -------------------------------------------------------------------------------- /typst-sympy-calculator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrangeX4/vscode-typst-sympy-calculator/HEAD/typst-sympy-calculator.gif -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | test/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/jsconfig.json 8 | **/*.map 9 | **/.eslintrc.json 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "checkJs": false, /* Typecheck .js files. */ 6 | "lib": [ 7 | "es6" 8 | ] 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/suite/extension.test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | const vscode = require('vscode'); 6 | // const myExtension = require('../extension'); 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 2018, 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-const-assign": "warn", 18 | "no-this-before-super": "warn", 19 | "no-undef": "warn", 20 | "no-unreachable": "warn", 21 | "no-unused-vars": "warn", 22 | "constructor-super": "warn", 23 | "valid-typeof": "warn" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/runTest.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const { runTests } = require('vscode-test'); 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../'); 10 | 11 | // The path to the extension test script 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ] 15 | }, 16 | { 17 | "name": "Extension Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "args": [ 21 | "--extensionDevelopmentPath=${workspaceFolder}", 22 | "--extensionTestsPath=${workspaceFolder}/test/suite/index" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/suite/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Mocha = require('mocha'); 3 | const glob = require('glob'); 4 | 5 | function run() { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | module.exports = { 41 | run 42 | }; 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OrangeX4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | import json 3 | import base64 4 | import sympy 5 | from sympy import factor, expand, apart, expand_trig 6 | import math 7 | from TypstCalculatorServer import TypstCalculatorServer, VERSION 8 | 9 | app = Flask(__name__) 10 | 11 | server = TypstCalculatorServer() 12 | calc = server.calculator 13 | 14 | typst = server.typst 15 | typst2sympy = server.sympy 16 | exec_code = server.exec 17 | subs, simplify, evalf, solve = server.subs, server.simplify, server.evalf, server.solve 18 | set_variance, unset_variance, clear_variance = server.set_variance, server.unset_variance, server.clear_variance 19 | operator, relation_op, additive_op, mp_op, postfix_op, reduce_op, func, func_mat, constant = calc.get_decorators() 20 | var = server.var 21 | 22 | 23 | def base64_decode(s: str): 24 | return base64.b64decode(s).decode('utf-8') 25 | 26 | 27 | @app.route('/') 28 | def main(): 29 | return 'Typst Sympy Calculator Server' 30 | 31 | 32 | @app.route('/version', methods=['GET']) 33 | def get_version(): 34 | return { 35 | 'data': VERSION, 36 | 'error': '' 37 | } 38 | 39 | 40 | @app.route('/init', methods=['POST']) 41 | def post_init(): 42 | try: 43 | return { 44 | 'data': server.init(base64_decode(request.json['typst_file'])), 45 | 'error': '' 46 | } 47 | except Exception as e: 48 | return { 49 | 'data': '', 50 | 'error': str(e) 51 | } 52 | 53 | 54 | @app.route('/simplify', methods=['POST']) 55 | def post_simplify(): 56 | try: 57 | return { 58 | 'data': typst(simplify(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file']))), 59 | 'error': '' 60 | } 61 | except Exception as e: 62 | return { 63 | 'data': '', 64 | 'error': str(e) 65 | } 66 | 67 | 68 | @app.route('/evalf', methods=['POST']) 69 | def post_evalf(): 70 | try: 71 | return { 72 | 'data': typst(evalf(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file']))), 73 | 'error': '' 74 | } 75 | except Exception as e: 76 | return { 77 | 'data': '', 78 | 'error': str(e) 79 | } 80 | 81 | 82 | @app.route('/solve', methods=['POST']) 83 | def post_solve(): 84 | try: 85 | return { 86 | 'data': typst(solve(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file']))), 87 | 'error': '' 88 | } 89 | except Exception as e: 90 | return { 91 | 'data': '', 92 | 'error': str(e) 93 | } 94 | 95 | 96 | @app.route('/factor', methods=['POST']) 97 | def post_factor(): 98 | try: 99 | return { 100 | 'data': typst(factor(subs(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file'])))), 101 | 'error': '' 102 | } 103 | except Exception as e: 104 | return { 105 | 'data': '', 106 | 'error': str(e) 107 | } 108 | 109 | 110 | @app.route('/expand', methods=['POST']) 111 | def post_expand(): 112 | try: 113 | return { 114 | 'data': typst(expand(apart(expand_trig(subs(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file'])))))), 115 | 'error': '' 116 | } 117 | except Exception as _: 118 | try: 119 | return { 120 | 'data': typst(expand(expand_trig(subs(base64_decode(request.json['typst_math']), base64_decode(request.json['typst_file']))))), 121 | 'error': '' 122 | } 123 | except Exception as e: 124 | return { 125 | 'data': '', 126 | 'error': str(e) 127 | } 128 | 129 | 130 | @app.route('/variances', methods=['GET']) 131 | def get_variances(): 132 | try: 133 | result = {} 134 | for key in var: 135 | result[key] = typst(var[key]) 136 | return { 137 | 'data': result, 138 | 'error': '' 139 | } 140 | except Exception as e: 141 | return { 142 | 'data': '', 143 | 'error': str(e) 144 | } 145 | 146 | 147 | @app.route('/python', methods=['POST']) 148 | def run_python(): 149 | try: 150 | rv = None 151 | try: 152 | rv = eval(base64_decode(request.json['code'])) 153 | except SyntaxError: 154 | # replace all \t with 4 spaces 155 | python_code = base64_decode(request.json['code']).replace('\t', ' ') 156 | # remove leading indent from python code 157 | lines = python_code.split('\n') 158 | indent = len(lines[0]) - len(lines[0].lstrip()) 159 | new_python_code = '' 160 | for line in lines: 161 | assert set(' ' + line[:indent]) == set(' '), 'IndentationError' 162 | new_python_code += line[indent:] + '\n' 163 | exec(new_python_code) 164 | return { 165 | 'data': str(rv), 166 | 'error': '' 167 | } 168 | except Exception as e: 169 | return { 170 | 'data': '', 171 | 'error': str(e) 172 | } 173 | 174 | 175 | if __name__ == '__main__': 176 | app.run(host='127.0.0.1', port=7396) 177 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-typst-sympy-calculator", 3 | "displayName": "Typst Sympy Calculator", 4 | "publisher": "OrangeX4", 5 | "description": "VS Code extension for Typst math calculating, includes Arithmetic, Calculous, Matrix, Custom Variances and Funcions by yourself", 6 | "icon": "icon.png", 7 | "version": "0.5.1", 8 | "engines": { 9 | "vscode": "^1.46.0" 10 | }, 11 | "categories": [ 12 | "Data Science", 13 | "Notebooks" 14 | ], 15 | "activationEvents": [ 16 | "onCommand:vscode-typst-sympy-calculator.reimport", 17 | "onCommand:vscode-typst-sympy-calculator.variances", 18 | "onCommand:vscode-typst-sympy-calculator.equal", 19 | "onCommand:vscode-typst-sympy-calculator.replace", 20 | "onCommand:vscode-typst-sympy-calculator.factor", 21 | "onCommand:vscode-typst-sympy-calculator.expand", 22 | "onCommand:vscode-typst-sympy-calculator.numerical", 23 | "onCommand:vscode-typst-sympy-calculator.solve", 24 | "onCommand:vscode-typst-sympy-calculator.python" 25 | ], 26 | "main": "./extension.js", 27 | "contributes": { 28 | "configuration": [ 29 | { 30 | "title": "Typst-Sympy-Calculator", 31 | "properties": { 32 | "vscode-typst-sympy-calculator.windows": { 33 | "type": "string", 34 | "default": "python", 35 | "description": "Path of python for Windows." 36 | }, 37 | "vscode-typst-sympy-calculator.linux": { 38 | "type": "string", 39 | "default": "python3", 40 | "description": "Path of python for Linux." 41 | }, 42 | "vscode-typst-sympy-calculator.macos": { 43 | "type": "string", 44 | "default": "python3", 45 | "description": "Path of python for Mac OS." 46 | } 47 | } 48 | } 49 | ], 50 | "commands": [ 51 | { 52 | "category": "Typst-Sympy-Calculator", 53 | "command": "vscode-typst-sympy-calculator.reimport", 54 | "title": "Reimport Files for Typst Sympy Calculator" 55 | }, 56 | { 57 | "category": "Typst-Sympy-Calculator", 58 | "command": "vscode-typst-sympy-calculator.variances", 59 | "title": "Show Current Variances" 60 | }, 61 | { 62 | "category": "Typst-Sympy-Calculator", 63 | "command": "vscode-typst-sympy-calculator.equal", 64 | "title": "Append result of selected expression" 65 | }, 66 | { 67 | "category": "Typst-Sympy-Calculator", 68 | "command": "vscode-typst-sympy-calculator.replace", 69 | "title": "Replace expression with its result" 70 | }, 71 | { 72 | "category": "Typst-Sympy-Calculator", 73 | "command": "vscode-typst-sympy-calculator.factor", 74 | "title": "Factor expression" 75 | }, 76 | { 77 | "category": "Typst-Sympy-Calculator", 78 | "command": "vscode-typst-sympy-calculator.expand", 79 | "title": "Expand expression" 80 | }, 81 | { 82 | "category": "Typst-Sympy-Calculator", 83 | "command": "vscode-typst-sympy-calculator.numerical", 84 | "title": "Calculate the numerical expression of selection" 85 | }, 86 | { 87 | "category": "Typst-Sympy-Calculator", 88 | "command": "vscode-typst-sympy-calculator.solve", 89 | "title": "Solve equations of selection" 90 | }, 91 | { 92 | "category": "Typst-Sympy-Calculator", 93 | "command": "vscode-typst-sympy-calculator.python", 94 | "title": "Calculate Python expression" 95 | } 96 | ], 97 | "keybindings": [ 98 | { 99 | "command": "vscode-typst-sympy-calculator.equal", 100 | "key": "Shift+Ctrl+Alt+E", 101 | "mac": "Shift+Cmd+Alt+E", 102 | "when": "editorTextFocus && editorHasSelection" 103 | }, 104 | { 105 | "command": "vscode-typst-sympy-calculator.replace", 106 | "key": "Shift+Ctrl+Alt+R", 107 | "mac": "Shift+Cmd+Alt+R", 108 | "when": "editorTextFocus && editorHasSelection" 109 | }, 110 | { 111 | "command": "vscode-typst-sympy-calculator.factor", 112 | "key": "Shift+Ctrl+Alt+F", 113 | "mac": "Shift+Cmd+Alt+F", 114 | "when": "editorTextFocus && editorHasSelection" 115 | }, 116 | { 117 | "command": "vscode-typst-sympy-calculator.expand", 118 | "key": "Shift+Ctrl+Alt+X", 119 | "mac": "Shift+Cmd+Alt+X", 120 | "when": "editorTextFocus && editorHasSelection" 121 | }, 122 | { 123 | "command": "vscode-typst-sympy-calculator.numerical", 124 | "key": "Shift+Ctrl+Alt+N", 125 | "mac": "Shift+Cmd+Alt+N", 126 | "when": "editorTextFocus && editorHasSelection" 127 | }, 128 | { 129 | "command": "vscode-typst-sympy-calculator.solve", 130 | "key": "Shift+Ctrl+Alt+S", 131 | "mac": "Shift+Cmd+Alt+S", 132 | "when": "editorTextFocus && editorHasSelection" 133 | }, 134 | { 135 | "command": "vscode-typst-sympy-calculator.python", 136 | "key": "Shift+Ctrl+Alt+P", 137 | "mac": "Shift+Cmd+Alt+P", 138 | "when": "editorTextFocus && editorHasSelection" 139 | } 140 | ] 141 | }, 142 | "scripts": { 143 | "lint": "eslint .", 144 | "pretest": "npm run lint", 145 | "test": "node ./test/runTest.js" 146 | }, 147 | "devDependencies": { 148 | "@types/vscode": "^1.46.0", 149 | "@types/glob": "^7.1.3", 150 | "@types/mocha": "^8.0.0", 151 | "@types/node": "^12.11.7", 152 | "eslint": "^7.9.0", 153 | "glob": "^7.1.6", 154 | "mocha": "^8.1.3", 155 | "typescript": "^4.0.2", 156 | "vscode-test": "^1.4.0" 157 | }, 158 | "dependencies": {}, 159 | "license": "MIT", 160 | "bugs": { 161 | "url": "https://github.com/OrangeX4/vscode-typst-sympy-calculator/issues" 162 | }, 163 | "repository": { 164 | "type": "git", 165 | "url": "https://github.com/OrangeX4/vscode-typst-sympy-calculator" 166 | }, 167 | "homepage": "https://orangex4.cool" 168 | } 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](icon.png) 2 | 3 | # Typst Sympy Calculator 4 | 5 | ## About 6 | 7 | `Typst Sympy Calculator` parses **typst math expressions** and converts it into the equivalent **SymPy form**. Then, **calculate it** and convert to typst result. 8 | 9 | It is designed for providing **people writing in typst** a ability to calculate something when writing math expression. It is based on `Python`, `Sympy` and [`typst-sympy-calculator`](https://github.com/OrangeX4/typst-sympy-calculator) module. 10 | 11 | PS: If you want to install the extension, **PLEASE READ THE INSTALL DESCRIPTION!** 12 | 13 | 14 | ## Features 15 | 16 | ![Demo](https://picgo-1258602555.cos.ap-nanjing.myqcloud.com/typst-sympy-calculator.gif) 17 | 18 | - **Default Math:** 19 | - [x] **Arithmetic:** Add (`+`), Sub (`-`), Dot Mul (`dot`), Cross Mul (`times`), Frac (`/`), Power (`^`), Abs (`|x|`), Sqrt (`sqrt`), etc... 20 | - [x] **Alphabet:** `a - z`, `A - Z`, `alpha - omega`, Subscript (`x_1`), Accent Bar(`hat(x)`), etc... 21 | - [x] **Common Functions:** `gcd`, `lcm`, `floor`, `ceil`, `max`, `min`, `log`, `ln`, `exp`, `sin`, `cos`, `tan`, `csc`, `sec`, `cot`, `arcsin`, `sinh`, `arsinh`, etc... 22 | - [x] **Funcion Symbol:** `f(x)`, `f(x-1,)`, `g(x,y)`, etc... 23 | - [x] **Calculous:** Limit `lim_(x -> oo) 1/x`, Integration `integral_1^2 x dif x`, etc... 24 | - [x] **Calculous:** Derivation (`dif/(dif x) (x^2 + 1)` is not supported, but you can use `derivative(expr, var)` instead), etc... 25 | - [x] **Reduce:** Sum `sum_(k=1)^oo (1/2)^k`, Product `product_(k=1)^oo (1/2)^k`, etc... 26 | - [x] **Eval At:** Evalat `x^2 bar_(x = 2)`, `x^2 "|"_(x = 2)`, etc... 27 | - [x] **Linear Algebra:** Matrix to raw echelon form `rref`, Determinant `det`, Transpose `^T`, Inverse `^(-1)`, etc... 28 | - [x] **Relations:** `==`, `>`, `>=`, `<`, `<=`, etc... 29 | - [x] **Solve Equation:** Single Equation `x + 1 = 2`, Multiple Equations `cases(x + y = 1, x - y = 2)`, etc... 30 | - [ ] **Logical:** `and`, `or`, `not`, etc... 31 | - [ ] **Set Theory:** `in`, `sect`, `union`, `subset`, etc... 32 | - [x] **Other:** Binomial `binom(n, k)` ... 33 | - **Custom Math (in typst file):** 34 | - [x] **Define Accents:** `#let acc(x) = math.accent(x, math.grave)` 35 | - [x] **Define Operators:** `#let add = math.op("add")` 36 | - [x] **Define Symbols:** `#let xy = math.italic("xy")` or `#let mail = symbol("🖂", ("stamped", "🖃"),)` 37 | - [x] **Define Functions:** 38 | ```py 39 | # typst-calculator 40 | @func() 41 | def convert_add(a, b): 42 | return a + b 43 | ``` 44 | - **Typst Math Printer:** 45 | - [x] Complete `TypstMathPrinter` in `TypstConverter.py` 46 | - [ ] Custom Printer for `TypstCalculator.py` and `TypstCalculatorServer.py` 47 | - **VS Code Extension:** 48 | - [x] Develop a VS Code Extension for `Typst Calculator` 49 | 50 | 51 | 52 | ## Install 53 | 54 | **IT IS IMPORTANT!** 55 | 56 | **IT IS IMPORTANT!** 57 | 58 | **IT IS IMPORTANT!** 59 | 60 | Before you use the extension, please install python and two python modules: `typst-sympy-calculator` and `Flask`. 61 | 62 | Install **Python** in [Python.org](https://www.python.org/), and then install **NECESSARY modules** by running: 63 | 64 | ```sh 65 | pip install typst-sympy-calculator 66 | pip install Flask 67 | ``` 68 | 69 | Then import the typst template file [`typst-sympy-calculator.typ`](https://github.com/OrangeX4/typst-sympy-calculator.typ) into your typst file. It will be like: 70 | 71 | ```typst 72 | #import "typst-sympy-calculator.typ": * 73 | ``` 74 | 75 | This step is not necessary, but it can provide you with examples of custom functions. 76 | 77 | 78 | ## Usage 79 | 80 | ![Demo](typst-sympy-calculator.gif) 81 | 82 | ### Typst to Typst 83 | 84 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + E` (equal) to get the result of the selected Typst text. It will be like: 85 | 86 | ```typst 87 | // Before 88 | $ integral x dif x $ 89 | 90 | // After 91 | $ integral x dif x = 1/2 x^2 $ 92 | ``` 93 | 94 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + R` (replace) to get the result of the selected Typst text. It will be like: 95 | 96 | ```typst 97 | // Before 98 | $ integral x dif x $ 99 | 100 | // After 101 | $ 1/2 x^2 $ 102 | ``` 103 | 104 | 105 | ### Factor and Expand 106 | 107 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + F` (factor) to get the factor of the selected Typst text. It will be like: 108 | 109 | ```typst 110 | // Before 111 | $ x^2 + 2 x y + y^2 $ 112 | 113 | // After 114 | $ (x + y)^2 $ 115 | ``` 116 | 117 | If you are using **windows**, the shortcut `Shift + Ctrl + Alt + F` may be invalid, you can set another shortcut for it. 118 | 119 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + X` (expand) to get the expand of the selected Typst text. It will be like: 120 | 121 | ```typst 122 | // Before 123 | $ (x + y)^2 $ 124 | 125 | // After 126 | $ x^2 + 2 x y + y^2 $ 127 | ``` 128 | 129 | ### Typst to Numerical Result 130 | 131 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + N` (numerical) to get the numerical result of the selected Typst text. It will be like: 132 | 133 | ```typst 134 | // Before 135 | sqrt(2) 136 | 137 | // After 138 | 1.41421356237310 139 | ``` 140 | 141 | ### Solve Equations and Inequations 142 | 143 | You can **SELECT** some text, and press `Shift + Ctrl + Alt + S` (solve) to solve the equations of the selected Typst text. It will be like: 144 | 145 | ```typst 146 | // Before 147 | x + y = 1 148 | 149 | // After 150 | y = 1 - x, x = 1 - y 151 | 152 | // Before 153 | cases(x + y = 1, x - y = 1) 154 | 155 | // After 156 | cases(x = 1, y = 0) 157 | 158 | // Before 159 | x + 3 < 1 160 | 161 | // After 162 | -oo < x and x < -2 163 | ``` 164 | 165 | ### Variances 166 | 167 | You can **ASSIGN** variance a value using same assignment form in typst: 168 | 169 | ```typst 170 | #let x = 1 171 | 172 | // Before 173 | $ x $ 174 | 175 | // Shift + Ctrl + E 176 | // After 177 | $ x = 1 $ 178 | ``` 179 | 180 | PS: You can use grammar like `y == x + 1` to describe the relation of equality. 181 | 182 | If you want to see the bonding of variances, you can press `Shift + Ctrl + P`, and input `typst-sympy-calculator: Show Current variances`, then you will get data like: 183 | 184 | ```typst 185 | y = x + 1 186 | z = 2 x 187 | ``` 188 | 189 | ### Functions 190 | 191 | You can **DEFINE** a function using same form in typst: 192 | 193 | ```typst 194 | #let f = math.op("f") 195 | 196 | // Before 197 | $ f(1) + f(1) $ 198 | 199 | // Shift + Ctrl + E 200 | // After 201 | $ f(1) + f(1) = 2 f(1) $ 202 | ``` 203 | 204 | ### Symbols 205 | 206 | You can **DEFINE** a symbol using same form in typst: 207 | 208 | ```typst 209 | #let xy = math.italic("xy") 210 | #let email = symbol("🖂", ("stamped", "🖃"),) 211 | 212 | $ xy + email + email.stamped $ 213 | ``` 214 | 215 | ### Accents 216 | 217 | You can **DEFINE** a accent using same form in typst: 218 | 219 | ```typst 220 | #let acc(x) = math.accent(x, math.grave) 221 | 222 | $ acc(x) $ 223 | ``` 224 | 225 | ### Decorators for Operators 226 | 227 | You can **DEFINE** a operator using same form in typst: 228 | 229 | ```typst 230 | #let add = math.op("+") 231 | 232 | '''typst-calculator 233 | @additive_op() 234 | def convert_add(a, b): 235 | return a + b 236 | ''' 237 | 238 | // Before 239 | $ 1 add 1 $ 240 | 241 | // Shift + Ctrl + E 242 | // After 243 | $ 1 add 1 = 2 $ 244 | ``` 245 | 246 | Or just use `'''typst-sympy-calculator` or `'''python \n # typst-calculator` to define a operator. 247 | 248 | there are some decorators you can use: 249 | 250 | - `@operator(type='ADDITIVE_OP', convert_ast=convert_ast, name=name, ast=False)`: Define a common operator; 251 | - `@func()`: Define a function, receive args list; 252 | - `@func_mat()`: Define a matrix function, receive single arg `matrix`; 253 | - `@constant()`: Define a constant, receive no args but only return a constant value; 254 | - `@relation_op()`: Define a relation operator, receive args `a` and `b`; 255 | - `@additive_op()`: Define a additive operator, receive args `a` and `b`; 256 | - `@mp_op()`: Define a multiplicative operator, receive args `a` and `b`; 257 | - `@postfix_op()`: Define a postfix operator, receive args `a`; 258 | - `@reduce_op()`: Define a reduce operator, receive args `expr` and `args = (symbol, sub, sup)`; 259 | 260 | It is important that the function name MUST be `def convert_{operator_name}`, or you can use decorator arg `@func(name='operator_name')`, and the substring `_dot_` will be replaced by `.`. 261 | 262 | There are some examples (from [DefaultTypstCalculator.py](https://github.com/OrangeX4/typst-sympy-calculator/blob/main/DefaultTypstCalculator.py)): 263 | 264 | ```python 265 | # Functions 266 | @func() 267 | def convert_binom(n, k): 268 | return sympy.binomial(n, k) 269 | 270 | # Matrix 271 | @func_mat() 272 | def convert_mat(mat): 273 | return sympy.Matrix(mat) 274 | 275 | # Constants 276 | @constant() 277 | def convert_oo(): 278 | return sympy.oo 279 | 280 | # Relation Operators 281 | @relation_op() 282 | def convert_eq(a, b): 283 | return sympy.Eq(a, b) 284 | 285 | # Additive Operators 286 | @additive_op() 287 | def convert_plus(a, b): 288 | return a + b 289 | 290 | # Mp Operators 291 | @mp_op() 292 | def convert_times(a, b): 293 | return a * b 294 | 295 | # Postfix Operators 296 | @postfix_op() 297 | def convert_degree(expr): 298 | return expr / 180 * sympy.pi 299 | 300 | # Reduces 301 | @reduce_op() 302 | def convert_sum(expr, args): 303 | # symbol, sub, sup = args 304 | return sympy.Sum(expr, args) 305 | ``` 306 | 307 | 308 | ### Python 309 | 310 | You can calculate a python expression by `Shift + Ctrl + Alt + P`. 311 | 312 | **You can use all sympy expression in it.** 313 | 314 | For example, you can get variances you assigned by: 315 | 316 | ``` python 317 | # Before 318 | typst(var['y']) 319 | 320 | # After 321 | typst(var['y']) = x + 1 322 | ``` 323 | 324 | Calculator the roots of the equation: 325 | 326 | ``` python 327 | # Before 328 | sympy.solve([2 * x - y - 3, 3 * x + y - 7],[x, y]) 329 | 330 | # After 331 | sympy.solve([2 * x - y - 3, 3 * x + y - 7],[x, y]) = {x: 2, y: 1} 332 | ``` 333 | 334 | 335 | ## Thanks 336 | 337 | - [augustt198 / latex2sympy](https://github.com/augustt198/latex2sympy) 338 | - [purdue-tlt / latex2sympy](https://github.com/purdue-tlt/latex2sympy) 339 | - [ANTLR](https://www.antlr.org/) 340 | - [Sympy](https://www.sympy.org/en/index.html) 341 | 342 | 343 | ## License 344 | 345 | This project is licensed under the MIT License. -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | const vscode = require('vscode') 4 | 5 | // this method is called when your extension is activated 6 | // your extension is activated the very first time the command is executed 7 | 8 | const os = require('os') 9 | const platform = os.platform() 10 | let py 11 | let is_init = false 12 | 13 | /** 14 | * @param {vscode.ExtensionContext} context 15 | */ 16 | function activate(context) { 17 | const { exec, spawn } = require('child_process') 18 | const http = require('http') 19 | 20 | const port = 7396 21 | 22 | let python_path = '' 23 | 24 | switch (platform) { 25 | case 'darwin': 26 | python_path = vscode.workspace.getConfiguration('vscode-typst-sympy-calculator').get('macos') 27 | break; 28 | case 'linux': 29 | python_path = vscode.workspace.getConfiguration('vscode-typst-sympy-calculator').get('linux') 30 | break; 31 | case 'win32': 32 | python_path = vscode.workspace.getConfiguration('vscode-typst-sympy-calculator').get('windows') 33 | break; 34 | default: 35 | vscode.window.showErrorMessage('Unknown operate system.') 36 | return 37 | } 38 | 39 | // run auto update 40 | exec(python_path + ' -m pip install --upgrade typst-sympy-calculator', (err, stdout, stderr) => { 41 | 42 | if (err) { 43 | console.log(err) 44 | } 45 | 46 | if (stderr) { 47 | console.log(stderr) 48 | } 49 | 50 | if (stdout) { 51 | console.log(stdout) 52 | } 53 | }) 54 | 55 | // run server 56 | py = spawn(python_path, [context.asAbsolutePath("server.py")]) 57 | 58 | py.on('error', (err) => { 59 | console.log(err) 60 | vscode.window.showErrorMessage('Running python failed... Please read the guide and make sure you have install "python", "typst-sympy-calculator" and "Flask"') 61 | }) 62 | 63 | py.on('exit', (code) => { 64 | console.log(`Exit Code: ${code}`) 65 | vscode.window.showErrorMessage('Running python failed... Please make sure you have "typst-sympy-calculator >= 0.4.2" and "Flask"') 66 | vscode.window.showErrorMessage('You can update it by "pip install --upgrade typst-sympy-calculator" and reboot your computer') 67 | }) 68 | 69 | function base64(s) { 70 | return Buffer.from(s).toString('base64') 71 | } 72 | 73 | 74 | /** 75 | * @param {string} data 76 | * @param {string} path 77 | * @param {function} onSuccess 78 | * @param {function} onError 79 | */ 80 | function post(data, path, onSuccess, onError) { 81 | for (let key in data) { 82 | if (typeof data[key] === 'string') { 83 | data[key] = base64(data[key]) 84 | } 85 | } 86 | const _data = JSON.stringify(data) 87 | 88 | const options = { 89 | hostname: '127.0.0.1', 90 | port: port, 91 | path: path, 92 | method: 'POST', 93 | headers: { 94 | 'Content-Type': 'application/json', 95 | 'Content-Length': _data.length 96 | } 97 | } 98 | 99 | const req = http.request(options, res => { 100 | res.on('data', data => { 101 | const result = JSON.parse(data) 102 | if (result.error) { 103 | onError(result.error) 104 | } else { 105 | onSuccess(result.data) 106 | } 107 | }) 108 | }) 109 | 110 | req.on('error', () => { 111 | vscode.window.showInformationMessage('Activating the server...\nPlease retry for a moment later.') 112 | }) 113 | 114 | req.write(_data) 115 | req.end() 116 | } 117 | 118 | /** 119 | * @param {string} path 120 | * @param {function} onSuccess 121 | */ 122 | function get(path, onSuccess, onError) { 123 | const options = { 124 | hostname: '127.0.0.1', 125 | port: port, 126 | path: path, 127 | method: 'GET' 128 | } 129 | 130 | const req = http.request(options, res => { 131 | res.on('data', data => { 132 | const result = JSON.parse(data) 133 | if (result.error) { 134 | onError(result.error) 135 | } else { 136 | onSuccess(result.data) 137 | } 138 | }) 139 | }) 140 | 141 | req.on('error', () => { 142 | vscode.window.showInformationMessage('Activating the server...\r\nPlease retry for a moment later.') 143 | }) 144 | 145 | req.end() 146 | } 147 | 148 | 149 | function init_if_not(typst_file, callback) { 150 | if (is_init) { 151 | callback() 152 | } else { 153 | post({ typst_file: typst_file }, '/init', (data) => { 154 | vscode.window.showInformationMessage('Import files: ' + JSON.stringify(data)) 155 | is_init = true 156 | callback() 157 | }, (err) => { 158 | vscode.window.showErrorMessage(err) 159 | }) 160 | } 161 | } 162 | 163 | 164 | context.subscriptions.push( 165 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.equal', function () { 166 | let editor = vscode.window.activeTextEditor 167 | if (!editor) { return } 168 | let doc = editor.document 169 | let selection = editor.selection 170 | let typst_math = doc.getText(selection) 171 | let typst_file = doc.fileName 172 | 173 | init_if_not(typst_file, () => { 174 | post({ 175 | typst_math: typst_math, 176 | typst_file: typst_file 177 | }, 'simplify', (data) => { 178 | let editor = vscode.window.activeTextEditor 179 | if (!editor) { return } 180 | editor.edit((edit) => { 181 | edit.insert(selection.end, ' = ' + data) 182 | }) 183 | }, (err) => { 184 | vscode.window.showErrorMessage(err) 185 | }) 186 | }) 187 | }) 188 | ) 189 | 190 | context.subscriptions.push( 191 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.numerical', function () { 192 | let editor = vscode.window.activeTextEditor 193 | if (!editor) { return } 194 | let doc = editor.document 195 | let selection = editor.selection 196 | let typst_math = doc.getText(selection) 197 | let typst_file = doc.fileName 198 | 199 | init_if_not(typst_file, () => { 200 | post({ 201 | typst_math: typst_math, 202 | typst_file: typst_file 203 | }, 'evalf', (data) => { 204 | let editor = vscode.window.activeTextEditor 205 | if (!editor) { return } 206 | editor.edit((edit) => { 207 | edit.replace(selection, data) 208 | }) 209 | }, (err) => { 210 | vscode.window.showErrorMessage(err) 211 | }) 212 | }) 213 | }) 214 | ) 215 | 216 | context.subscriptions.push( 217 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.solve', function () { 218 | let editor = vscode.window.activeTextEditor 219 | if (!editor) { return } 220 | let doc = editor.document 221 | let selection = editor.selection 222 | let typst_math = doc.getText(selection) 223 | let typst_file = doc.fileName 224 | 225 | init_if_not(typst_file, () => { 226 | post({ 227 | typst_math: typst_math, 228 | typst_file: typst_file 229 | }, 'solve', (data) => { 230 | let editor = vscode.window.activeTextEditor 231 | if (!editor) { return } 232 | editor.edit((edit) => { 233 | edit.replace(selection, data) 234 | }) 235 | }, (err) => { 236 | vscode.window.showErrorMessage(err) 237 | }) 238 | }) 239 | }) 240 | ) 241 | 242 | 243 | context.subscriptions.push( 244 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.factor', function () { 245 | let editor = vscode.window.activeTextEditor 246 | if (!editor) { return } 247 | let doc = editor.document 248 | let selection = editor.selection 249 | let typst_math = doc.getText(selection) 250 | let typst_file = doc.fileName 251 | 252 | init_if_not(typst_file, () => { 253 | post({ 254 | typst_math: typst_math, 255 | typst_file: typst_file 256 | }, 'factor', (data) => { 257 | let editor = vscode.window.activeTextEditor 258 | if (!editor) { return } 259 | editor.edit((edit) => { 260 | edit.replace(selection, data) 261 | }) 262 | }, (err) => { 263 | vscode.window.showErrorMessage(err) 264 | }) 265 | }) 266 | }) 267 | ) 268 | 269 | 270 | context.subscriptions.push( 271 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.expand', function () { 272 | let editor = vscode.window.activeTextEditor 273 | if (!editor) { return } 274 | let doc = editor.document 275 | let selection = editor.selection 276 | let typst_math = doc.getText(selection) 277 | let typst_file = doc.fileName 278 | 279 | init_if_not(typst_file, () => { 280 | post({ 281 | typst_math: typst_math, 282 | typst_file: typst_file 283 | }, 'expand', (data) => { 284 | let editor = vscode.window.activeTextEditor 285 | if (!editor) { return } 286 | editor.edit((edit) => { 287 | edit.replace(selection, data) 288 | }) 289 | }, (err) => { 290 | vscode.window.showErrorMessage(err) 291 | }) 292 | }) 293 | }) 294 | ) 295 | 296 | context.subscriptions.push( 297 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.replace', function () { 298 | let editor = vscode.window.activeTextEditor 299 | if (!editor) { return } 300 | let doc = editor.document 301 | let selection = editor.selection 302 | let typst_math = doc.getText(selection) 303 | let typst_file = doc.fileName 304 | 305 | init_if_not(typst_file, () => { 306 | post({ 307 | typst_math: typst_math, 308 | typst_file: typst_file 309 | }, 'simplify', (data) => { 310 | let editor = vscode.window.activeTextEditor 311 | if (!editor) { return } 312 | editor.edit((edit) => { 313 | edit.replace(selection, data) 314 | }) 315 | }, (err) => { 316 | vscode.window.showErrorMessage(err) 317 | }) 318 | }) 319 | }) 320 | ) 321 | 322 | context.subscriptions.push( 323 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.reimport', function () { 324 | let editor = vscode.window.activeTextEditor 325 | if (!editor) { return } 326 | let doc = editor.document 327 | let typst_file = doc.fileName 328 | post({ typst_file: typst_file }, '/init', (data) => { 329 | vscode.window.showInformationMessage('Import files: ' + JSON.stringify(data)) 330 | }, (err) => { 331 | vscode.window.showErrorMessage(err) 332 | }) 333 | }) 334 | ) 335 | 336 | context.subscriptions.push( 337 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.variances', function () { 338 | get('/variances', (data) => { 339 | let editor = vscode.window.activeTextEditor 340 | if (!editor) { return } 341 | editor.edit((edit) => { 342 | const result = '\r\n' + Object.keys(data).map((key) => key + ' = ' + data[key]).join('\r\n') 343 | edit.insert(editor.selection.end, result) 344 | }) 345 | }, (err) => { 346 | vscode.window.showErrorMessage(err) 347 | }) 348 | }) 349 | ) 350 | 351 | 352 | context.subscriptions.push( 353 | vscode.commands.registerCommand('vscode-typst-sympy-calculator.python', function () { 354 | let editor = vscode.window.activeTextEditor 355 | if (!editor) { return } 356 | let doc = editor.document 357 | let selection = editor.selection 358 | let code = doc.getText(selection) 359 | 360 | post({code: code}, '/python', (data) => { 361 | let editor = vscode.window.activeTextEditor 362 | if (!editor) { return } 363 | editor.edit((edit) => { 364 | edit.insert(selection.end, ' = ' + data) 365 | }) 366 | }, (err) => { 367 | vscode.window.showErrorMessage(err) 368 | }) 369 | }) 370 | ) 371 | } 372 | 373 | exports.activate = activate 374 | 375 | // this method is called when your extension is deactivated 376 | function deactivate() { 377 | py.kill() 378 | } 379 | 380 | module.exports = { 381 | activate, 382 | deactivate 383 | } 384 | --------------------------------------------------------------------------------