Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

mdmath.nvim is a Neovim plugin that renders LaTeX math equations inline in Markdown files using the Kitty Graphics Protocol. The plugin combines Lua (Neovim side) with Node.js (equation processing) to provide real-time math rendering.

## Architecture

The plugin has a dual-language architecture:

### Lua Components (lua/mdmath/)
- **init.lua**: Main plugin entry point, setup and command handlers
- **overlay.lua**: Core rendering system managing equation overlays in buffers
- **Processor.lua**: Lua-side process manager for Node.js communication
- **Equation.lua**: Equation state management and rendering logic
- **Image.lua**: Kitty Graphics Protocol image handling
- **build.lua**: Build system for Node.js dependencies
- **config.lua**: Plugin configuration management
- **tracker.lua**: File change tracking and update management

### Node.js Components (mdmath-js/src/)
- **processor.js**: Main Node.js process handling MathJax rendering
- **magick.js**: ImageMagick integration for SVG-to-PNG conversion
- **reader.js**: Stdin/stdout communication with Neovim process

## Development Commands

### Build System
```bash
# Build Node.js dependencies (equivalent to :MdMath build)
cd mdmath-js && npm install

# Manual build from Neovim
:MdMath build
```

### Plugin Commands
```vim
:MdMath enable " Enable for current buffer
:MdMath disable " Disable for current buffer
:MdMath clear " Refresh all equations
:MdMath build " Build/rebuild Node.js server
```

### Testing Setup
The plugin requires specific system dependencies:
- Node.js and npm
- ImageMagick v6/v7
- rsvg-convert (librsvg)
- Terminal with Kitty Graphics Protocol + Unicode Placeholders support

## Key Implementation Details

### Process Architecture
- Lua spawns a persistent Node.js process (`mdmath-js/src/processor.js`)
- Communication via stdin/stdout using a custom protocol
- Images cached in `/tmp/nvim-mdmath-{random}/`
- Each equation gets a unique ID for tracking

### Rendering Pipeline
1. TreeSitter parses markdown_inline for math expressions
2. Equations sent to Node.js process via Processor.lua
3. Node.js renders with MathJax, converts SVG→PNG via ImageMagick
4. Images displayed using Kitty Graphics Protocol via Image.lua
5. Overlay.lua manages positioning and buffer updates

### Configuration System
Plugin uses a centralized config in `config.lua` with options for:
- Filetypes, colors, scaling, update intervals
- Dynamic sizing, anticonceal behavior
- Internal vs display scaling separation
61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 103 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
description = "mdmath.nvim - LaTeX math equation renderer for Neovim";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};

nodeDeps = pkgs.buildNpmPackage {
pname = "mdmath-js";
version = "1.0.0";
src = ./mdmath-js;
npmDepsHash = "sha256-yUyLKZQGIibS/9nHWnh0yvtZqza3qEpN9UNqRaNK53Y=";
dontNpmBuild = true;
installPhase = ''
mkdir -p "$out"
cp -r . "$out/"
chmod +x "$out/src/processor.js"
'';
};

plugin = pkgs.vimUtils.buildVimPlugin {
pname = "mdmath.nvim";
version = "1.0.0";
src = pkgs.lib.cleanSource ./.;

doCheck = false; # Disable require checks

postPatch = ''
# Replace mdmath-js with pre-built version
rm -rf mdmath-js
cp -r ${nodeDeps} mdmath-js
chmod -R u+w mdmath-js

# Create a wrapper for processor.js with proper PATH
mv mdmath-js/src/processor.js mdmath-js/src/processor-unwrapped.js
cat > mdmath-js/src/processor.js << EOF
#!/usr/bin/env node
process.env.PATH = "${
pkgs.lib.makeBinPath [
pkgs.librsvg
pkgs.imagemagick
pkgs.nodejs
]
}" + ":" + (process.env.PATH || "");
import('./processor-unwrapped.js');
EOF
chmod +x mdmath-js/src/processor.js
'';

# Runtime dependencies available to the plugin
propagatedBuildInputs = with pkgs; [
nodejs
imagemagick
librsvg
];
};

# Test Neovim with plugin pre-configured
testNvim = pkgs.neovim.override {
configure = {
customRC = ''
lua << EOF
require('mdmath').setup()
'';
packages.mdmath = {
start = [
plugin
(pkgs.vimPlugins.nvim-treesitter.withPlugins (p: [
p.markdown
p.markdown_inline
]))
];
};
};
};
in
{
packages.default = plugin;
packages.mdmath-nvim = plugin;
packages.test-nvim = testNvim;
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
npm
imagemagick
librsvg
];
};
}
);
}
5 changes: 4 additions & 1 deletion lua/mdmath/Equation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ function Equation:_init(bufnr, row, col, text, opts)
end

local processor = Processor.from_bufnr(bufnr)
processor:request(self.equation, cell_width, cell_height, self.width, height, flags, color, function(res, err)
local preamble = type(config.preamble) == "function" and config.preamble(vim.api.nvim_buf_get_name(bufnr))
or config.preamble
local data = preamble .. self.equation
processor:request(data, cell_width, cell_height, self.width, height, flags, color, function(res, err)
if self.valid then
self:_create(res, err)
end
Expand Down
13 changes: 13 additions & 0 deletions lua/mdmath/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ local default_opts = {
-- WARNING: This do not affect how the images are displayed, only how many pixels are used to render them.
-- See `dynamic_scale` to modify the displayed size.
internal_scale = 1.0,

-- Commands that will be prepended to each equation
-- Can be a string or function(filename) -> string
preamble = "",
}

local _opts = nil
Expand Down Expand Up @@ -62,10 +66,19 @@ function M.validate()
dynamic = {opts.dynamic, 'boolean'},
dynamic_scale = {opts.dynamic_scale, 'number'},
internal_scale = {opts.internal_scale, 'number'},
preamble = {opts.preamble, {'string', 'function'}},
}

opts.foreground = require'mdmath.util'.hl_as_hex(opts.foreground)

-- Validate preamble function signature if it's a function
if type(opts.preamble) == "function" then
local ok, result = pcall(opts.preamble, "")
if not ok or type(result) ~= "string" then
error("preamble function must accept a filename (string) and return a string")
end
end

setmetatable(opts, {
__newindex = function()
error 'Attempt to modify read-only mdmath.nvim options.'
Expand Down