diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..eb4f12e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: "1.26" + + - name: Check formatting + run: | + diff=$(gofmt -l .) + if [ -n "$diff" ]; then + echo "Files not formatted:" + echo "$diff" + exit 1 + fi + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... -v diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2dea93a..5445e99 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,44 +14,40 @@ jobs: matrix: include: - runner: ubuntu-latest - target: x86_64-unknown-linux-gnu + goos: linux + goarch: amd64 asset: live-markdown-linux-x64 - runner: ubuntu-latest - target: aarch64-unknown-linux-gnu + goos: linux + goarch: arm64 asset: live-markdown-linux-arm64 - runner: macos-latest - target: x86_64-apple-darwin + goos: darwin + goarch: amd64 asset: live-markdown-darwin-x64 - runner: macos-latest - target: aarch64-apple-darwin + goos: darwin + goarch: arm64 asset: live-markdown-darwin-arm64 - runner: ubuntu-latest - target: x86_64-pc-windows-msvc + goos: windows + goarch: amd64 asset: live-markdown-windows-x64.exe runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 + - uses: actions/setup-go@v5 with: - deno-version: v2.x - - - name: Download vendored assets - working-directory: server - run: deno task setup + go-version: "1.26" - - name: Compile binary - working-directory: server - run: > - deno compile - --allow-net=localhost - --allow-read - --include ../client/ - --target ${{ matrix.target }} - --output ../bin/${{ matrix.asset }} - src/main.ts + - name: Build binary + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + run: go build -ldflags="-s -w" -o bin/${{ matrix.asset }} ./cmd/live-markdown - name: Upload release asset uses: softprops/action-gh-release@v2 @@ -68,4 +64,3 @@ jobs: uses: softprops/action-gh-release@v2 with: generate_release_notes: true - diff --git a/.gitignore b/.gitignore index 5bf9906..e4c8d67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ # Build output bin/ -# Vendored assets (downloaded at build time) -client/vendor/ - -# Dependencies -node_modules/ +# Go +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +vendor/ # OS .DS_Store diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..fde0234 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.26.1 diff --git a/CLAUDE.md b/CLAUDE.md index d304c7a..32f63e0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,12 +40,14 @@ Read before implementing: ## Tech Stack - **Neovim plugin**: Lua (no external dependencies) -- **Preview server**: Deno (TypeScript) -- **Markdown parser**: markdown-it (server-side rendering) -- **CSS**: github-markdown-css (loaded from CDN) -- **Diagrams**: mermaid.js (downloaded at build time from CDN, rendered client-side) +- **Preview server**: Go (single binary, `go:embed` for all assets) +- **Markdown parser**: goldmark + GFM extension (server-side rendering) +- **Syntax highlighting**: chroma (server-side, CSS class-based) +- **Math rendering**: KaTeX (client-side via auto-render) +- **CSS**: github-markdown-css (bundled in `static/`) +- **Diagrams**: mermaid.js (bundled in `static/`, rendered client-side) - **Communication**: Neovim <-> Server via stdin/stdout (JSON Lines), Server <-> Browser via WebSocket -- **Distribution**: `deno compile` single binary -> GitHub Releases (users do not need Deno) +- **Distribution**: Go cross-compile single binary -> GitHub Releases ## Implementation Rules @@ -72,10 +74,26 @@ Read before implementing: - **Neovim <-> Server communication**: stdin/stdout JSON Lines (not WebSocket) - **mermaid rendering**: Client-side (`mermaid.run()` in the browser, not server-side) -- **mermaid distribution**: Downloaded from CDN at build time to `client/vendor/mermaid.min.js`, served as `/vendor/mermaid.min.js` -- **github-markdown-css**: Loaded from CDN (cdnjs), not bundled -- **Scroll sync**: `data-source-line` attribute + `scrollIntoView()` -- **Fence rule**: Special handling to inject `data-source-line` on `
` tags while preserving class attributes +- **KaTeX rendering**: Client-side (`renderMathInElement()` in the browser) +- **Static assets**: All bundled in `static/` and committed to git (no CDN, no build-time downloads) +- **Scroll sync**: `data-source-line` attribute via goldmark AST transformer + `scrollIntoView()` +- **Image paths**: Server rewrites relative src to `/_local/`, served from local filesystem - **Browser launch**: Presets + arbitrary command strings executed directly - **Initial connection**: Server caches last rendered HTML, sends immediately on WebSocket connect - **Server shutdown**: Sends `close` message to browser, attempts `window.close()` + +## Project Structure + +``` +cmd/live-markdown/main.go # Go server entry point +internal/ # Go server internals + message/types.go # JSON Lines message types + jsonlines/ # stdin/stdout reader/writer + markdown/renderer.go # goldmark pipeline + source-line + image rewrite + server/server.go # HTTP + WebSocket server +assets.go # go:embed directives +static/ # Bundled assets (CSS, fonts, JS) +client/ # Browser client (index.html, preview.js) +lua/live-markdown/ # Neovim plugin (Lua) +scripts/install.sh # Binary installer from GitHub Releases +``` diff --git a/README.md b/README.md index 1109fe8..262662a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ ## Features -- Single binary — no runtime dependencies (Deno not required) +- Single binary — no runtime dependencies - Real-time preview with scroll sync - Syntax highlighting (light/dark auto-switch) - Math rendering (KaTeX) @@ -72,7 +72,7 @@ require("live-markdown").setup({ server = { port = 0, -- 0 = OS auto-assigns host = "localhost", - binary = nil, -- path to compiled binary (nil = use deno run) + binary = nil, -- path to compiled binary (nil = auto-detect bin/live-markdown) }, browser = { strategy = "auto", -- "auto" | "open" | "xdg-open" | custom command diff --git a/assets.go b/assets.go new file mode 100644 index 0000000..aeb8c4e --- /dev/null +++ b/assets.go @@ -0,0 +1,9 @@ +package livemarkdown + +import "embed" + +//go:embed client/* +var ClientFS embed.FS + +//go:embed static/css/* static/fonts/* static/js/katex.min.js static/js/mermaid.min.js static/js/contrib/auto-render.min.js +var StaticFS embed.FS diff --git a/client/index.html b/client/index.html index 217f81f..7a60a29 100644 --- a/client/index.html +++ b/client/index.html @@ -6,14 +6,14 @@ live-markdown preview - + - - - + + + - +