Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Pretty MathNew to Visual Studio Code? Get it now.
Pretty Math

Pretty Math

Malthe Møller Kristensen

|
8 installs
| (0) | Free
Renders the math expression under your cursor as real two-dimensional math in a panel, so precedence and grouping mistakes are obvious.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Pretty Math

A panel that draws the expression under your cursor the way you would write it on paper — fractions stacked, exponents raised, roots under a radical — so the structure of a long one-liner is visible at a glance.

w[i] = w[i] - eta * grad[i] / (math.sqrt(v[i]) + eps)
                η · grad_i
w_i  =  w_i  −  ──────────
                 √(v_i) + ε

The point is not decoration. a / b * c draws only b under the bar, so the classic precedence slip is visible instead of inferred.

The quadratic formula rendered in the panel

The same line with and without brackets

Using it

  • Open the panel: Ctrl+Alt+M (Cmd+Alt+M on macOS), or Pretty Math: Show Panel from the command palette. It docks next to Terminal and Problems.
  • Put the cursor anywhere on a line. The whole statement is picked up, including one that wraps over several lines, with comments and return / if / a C type declaration stripped off.
  • Select several lines to draw them all at once.
  • Hover a piece of the drawing to highlight where it came from in the editor; click it to select that range. The sub-expression your cursor sits in is outlined in the panel.
  • Pin freezes the panel on the current expression while you edit elsewhere.
  • Copy LaTeX puts the same expression on the clipboard as LaTeX.

What it understands

The parser follows the precedence of the language you are actually in, which is where most of the value is:

Written Drawn as Notes
a / b * c (a over b) · c left to right, as the language runs it
x ** 0.5, x ** (1/3) √x, ∛x
math.hypot(dx, dy) √(dx² + dy²) the identity behind the call
math.sin(t) ** 2 sin² t
a ^ b a ⊕ b in C/Python/JS, aᵇ in MATLAB/Julia/R ^ is XOR in most languages
flags & 0xFF == 0 (flags & 0xFF) = 0 in Python; flags & (0xFF = 0) in C & binds tighter than == in Python, looser in C
0 <= i < n a chain in Python; (0 ≤ i) < n in C and JS only some languages chain comparisons
n // 2 ⌊n/2⌋ in Python; a comment elsewhere
a and b or c (a ∧ b) ∨ c grouping drawn where precedence is easy to misread
a + b << c (a + b) ≪ c same
x if x > 0 else -x a two-row piecewise also cond ? a : b
theta_max, x1, v_0 θ_max, x₁, v₀ Greek names and subscripts
np.sqrt, self.mass √…, mass configured qualifiers dropped
a[i], A[i][j] a_i, A_i,j
sum(x**2 for x in xs) Σ with x ∈ xs underneath also prod, max, min, all (∀), any (∃)
[x*2 for x in pts] [2x ∣ x ∈ pts] set-builder form
lambda x: x**2, (x) => x*2 x ↦ x²
(double) sum / count, a as f64 the cast kept as a quiet annotation

Tested against Python, JavaScript/TypeScript, C/C++/C#, Java, Rust, Go, MATLAB, Octave, Julia and R conventions. Anything it cannot parse is reported with a caret under the offending character rather than silently dropped.

Calculus

SymPy's symbolic calls are drawn as the notation they stand for, and so are the numerical equivalents in NumPy and SciPy:

Written Drawn as
sp.diff(y, x) dy/dx as a stacked fraction
diff(x**2 + 1, x) d/dx (x² + 1) — operator form, operand bracketed
diff(f, x, 2), diff(f, (x, 2)) d²f/dx²
diff(f, x, y) ∂²f/∂x∂y — switches to ∂ for several variables
expr.diff(t) d(expr)/dt — the method form works too
sp.integrate(f, x) ∫ f dx
integrate(x**2, (x, 0, 1)) ∫ from 0 to 1, limits at the head and foot of the sign
integrate(f, x, y) ∫∫ f dx dy
np.trapz(y, x), simpson(y, x) ∫ y dx
quad(f, 0, 1) ∫ from 0 to 1 of f
sp.Sum(1/n**2, (n, 1, sp.oo)) Σ with n = 1 below and ∞ above
sp.Product(k, (k, 1, n)) ∏ with the same limits
sp.limit(f, x, 0), limit(f, x, 0, "+") lim with x → 0 underneath, one-sided as 0⁺
sp.Eq(F, m*a) F = m a — also Ne, Lt, Le, Gt, Ge
sp.Rational(1, 3) ⅓ as a fraction
np.diff(a), np.diff(a, n=2) Δa, Δ²a

That last row is the reason the qualifier matters: numpy.diff is a discrete difference, not a derivative, so drawing it as d/dx would invent a meaning that isn't there. sympy.diff and an unqualified diff are read as derivatives; anything from a NumPy-family namespace is read as a difference. Derivative, Integral, Sum, Product and Limit — the unevaluated classes — render the same as their lowercase functions.

Settings

Setting Default
prettyMath.autoUpdate true follow the cursor
prettyMath.fontSize 20 also the A- / A+ buttons
prettyMath.caretMeansPower auto auto reads ^ per language
prettyMath.multiplication implicit or dot, cross
prettyMath.indexAsSubscript true a[i] as a subscript
prettyMath.greekLetters true theta as θ
prettyMath.expandIdentities true exp, hypot, log1p, expm1
prettyMath.derivativeSymbol auto auto, d, or partial
prettyMath.stripNamespaces Math, math, np, sp, sympy, self, … qualifiers to drop
prettyMath.showLatex false LaTeX under the drawing
prettyMath.showTree false parse tree under the drawing
prettyMath.maxLines 40 statements drawn from one selection

Installing

There is no build step — it is plain JavaScript with no dependencies, so the folder is the extension.

Copy it into your editor's extensions directory and restart:

Editor Directory
VS Code ~/.vscode/extensions/pretty-math (%USERPROFILE%\.vscode\extensions\pretty-math)
VSCodium ~/.vscode-oss/extensions/pretty-math (%USERPROFILE%\.vscode-oss\extensions\pretty-math)

To hack on it instead, open this folder in VS Code and press F5 — that starts an Extension Development Host with the extension loaded.

To build a .vsix (needs Node on PATH):

npx @vscode/vsce package

Releasing

Pushing to main runs the tests on Linux, macOS and Windows. It does not release anything — an ordinary push never reaches anyone's editor.

A release happens when a version tag is pushed:

npm version patch      # or minor / major; edit package.json by hand if npm is absent
git push origin main --follow-tags

The tag has to match the version field or the workflow stops. It then builds the .vsix, attaches it to a GitHub release, and publishes to whichever marketplaces have a token set as a repository secret:

Secret Marketplace Where it comes from
VSCE_PAT Visual Studio Marketplace Azure DevOps personal access token, scope Marketplace → Manage, for the ru88erduck404 publisher
OVSX_PAT Open VSX, which VSCodium uses A token from open-vsx.org

Neither is required. With no secrets set the workflow still tests, builds, and attaches the .vsix to the GitHub release for hand installation.

extensionKind: ["ui", "workspace"] lets the extension run in the local extension host of a Remote-SSH, WSL or dev-container window, since it only reads the editor API and never the filesystem — so one install covers every remote.

Development

node test/run.js     # parser, renderer and LaTeX output
node test/host.js    # extension host against a stubbed VS Code API
node test/corpus.js  # real source lines: no crashes, no hangs, offsets intact
node test/demo.js    # writes test/demo.html to eyeball the layout in a browser
  • src/lexer.js, src/parser.js — tokenizer and Pratt parser, per-language flavors
  • src/prep.js — finds the statement around the cursor, keeps source offsets
  • src/render.js — the AST as HTML boxes, each tagged with its source range
  • src/latex.js — the same AST as LaTeX
  • media/panel.js — stretches fences and radicals to their content after layout

Licence

MIT — see LICENSE. Copyright © 2026 Malthe Møller Kristensen (@Ru88erduck404). No third-party code is included; the extension has no dependencies.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft