Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Editor Setup

Badness ships a language server. Start it with:

badness lsp

The server speaks the Language Server Protocol over stdio. Point your editor’s LSP client at the badness binary with the lsp argument and associate it with LaTeX (.tex) and BibTeX (.bib) files.

Settings can be supplied as initializationOptions at startup or through workspace/didChangeConfiguration, either as a bare object or namespaced under a badness key.

Formatter widths: lineWidth and indentWidth. They act as a fallback: a discovered badness.toml always wins outright, and absent one, your editor’s tab size (sent with each formatting request) overrides the indent width.

The language server is also the sole consumer of the [build] section of badness.toml, which locates the compile’s .aux artifacts; see the Configuration reference.

TEXMF discovery

How the language server discovers the installed TeX tree for package resolution: document links, package hover, go-to-definition, and installed-set completion. Where a TeX installation lives is a fact about the machine, not the project, so these settings come from the editor rather than badness.toml, and they never affect badness format or badness lint, whose output stays a pure function of the input regardless of what is installed.

A texmf object with three keys, all optional:

  • enabled (boolean, default true): whether to scan the TEXMF tree at all. When false, package resolution stays local to the document’s directory.
  • roots (array of paths, default []): extra TEXMF root directories to index in addition to (and ahead of) the discovered ones. Useful for a non-standard install that kpsewhich can’t see.
  • useKpsewhich (boolean, default true): whether to shell out to kpsewhich to discover the TEXMF tree roots. When false, discovery falls back to default-path heuristics only.
{ "texmf": { "enabled": true, "roots": ["/opt/texmf"], "useKpsewhich": true } }

Jump between a source line and the matching place in the compiled PDF.

Badness never typesets, and it never reads a .synctex.gz. Forward search works out three things — the file your cursor is in, the root document’s PDF, and the line number — and hands them to a viewer you configure. Every SyncTeX-aware viewer (zathura, Okular, SumatraPDF, Skim) links libsynctex and does the mapping itself, which is why they all want a file and a line rather than a coordinate. Inverse search runs in the other direction and is started by the viewer.

You need a PDF compiled with SyncTeX enabled — latexmk -pdf -synctex=1, or -synctex=1 passed to pdflatex/lualatex directly. Badness will not run that for you; use your existing build setup, or an extension like LaTeX Workshop.

Configuring the viewer

Which viewer is installed on your machine, and under what name, is a fact about the machine rather than the project — so these settings come from the editor, like TEXMF discovery, and not from badness.toml. Where the PDF lives is project data and belongs to the [build] section instead.

A forwardSearch object:

  • executable (string): the viewer program. Spawned directly, not through a shell, so it is a program name and never a command line — putting flags here ("zathura --synctex-forward") silently fails to launch. This is the most common misconfiguration.
  • args (array of strings): the viewer’s arguments. Required — there is no useful default, since every viewer spells forward search differently. Without it, forward search reports itself unconfigured.
  • ipcDir (path, optional): where inverse-search servers advertise themselves. An escape hatch for containers and sandboxes; see below.

Each argument may carry:

PlaceholderExpands to
%fthe .tex file the cursor is in
%pthe root document’s PDF
%lthe line number, counting from 1
%%fa literal %f

An argument wrapped entirely in " is passed through with the quotes stripped and nothing substituted — the escape hatch when a viewer needs a literal %.

Recipes, matching texlab’s, so an existing configuration ports unchanged:

Viewerexecutableargs
zathurazathura["--synctex-forward", "%l:1:%f", "%p"]
Okularokular["--unique", "file:%p#src:%l%f"]
SumatraPDFSumatraPDF["-reuse-instance", "%p", "-forward-search", "%f", "%l"]
Skimdisplayline["%l", "%p", "%f"]
Evinceevince-synctex["-f", "%l", "%p", "\"code -g %f:%l\""]
qpdfviewqpdfview["--unique", "%p#src:%f:%l:1"]
{
  "forwardSearch": {
    "executable": "zathura",
    "args": ["--synctex-forward", "%l:1:%f", "%p"]
  }
}

The server handles textDocument/forwardSearch, a custom request taking the standard { textDocument, position } params — the same method name and shape texlab uses, so a client written for texlab works unchanged. It never fails the request; it answers with a status:

StatusMeaning
0the viewer was launched
1the viewer would not start
2no PDF on disk, or the buffer has no path — build the document first
3no viewer configured

The capability is advertised as experimental.textDocumentForwardSearch.

If forward search opens the wrong PDF, or reports status 2 on a project that has been built, the root document is probably not being found — see root in the [build] reference.

Configure your viewer to run:

badness inverse-search --input "%f" --line "%l"

substituting the viewer’s own placeholders. For zathura that is:

zathura --synctex-editor-command "badness inverse-search --input %{input} --line %{line}"

Use --line0 instead if your viewer counts lines from zero. (--line1 is accepted as a synonym for --line, so a texlab configuration ports directly.)

The command finds the language server whose workspace contains the file and asks it to reveal the position, so an editor must already have that project open, and its LSP client must support window/showDocument. Servers whose client does not support it never register, which is why inverse search silently does nothing in an editor lacking it — the command says so when nothing is listening.

With several editor windows open, the server whose workspace root contains the file wins; the longest matching root is preferred, so nested projects resolve deterministically.

Servers advertise themselves in $BADNESS_IPC_DIR, else a per-user directory under your runtime directory ($XDG_RUNTIME_DIR), else the temporary directory. The forwardSearch.ipcDir setting overrides all of these — useful when the viewer and the server see different filesystems, as in a container or a remote development setup. Keep it short: a Unix socket path cannot exceed about 100 bytes, and badness says so explicitly in its log if yours does. On a system with no $XDG_RUNTIME_DIR and a /tmp shared between users, that last fallback is worth knowing about: the directory is created 0700, the advertisements 0600, and badness ignores any advertisement it does not own, so another user can neither read nor impersonate one.

One caveat inherent to SyncTeX: it maps the source as it was compiled. With unsaved edits, buffer line numbers and PDF line numbers drift apart until you rebuild.

Neovim

With the built-in vim.lsp client (Neovim 0.11+):

vim.lsp.config.badness = {
  cmd = { "badness", "lsp" },
  filetypes = { "tex", "latex", "plaintex", "bib" },
  root_markers = { "badness.toml", ".git" },
  init_options = { lineWidth = 80, indentWidth = 2 },
}
vim.lsp.enable("badness")

The init_options block is optional; omit it to use the defaults or a badness.toml.

VS Code

Install the Badness extension from the VS Code Marketplace or the Open VSX extension. It bundles a platform-specific badness binary and starts the language server automatically when you open a .tex file, so no separate CLI install is required.

The extension is configured through badness.* settings. By default it uses the bundled binary (badness.executableStrategy: "bundled"); set the strategy to environment to use a badness on your PATH, or path with badness.executablePath to point at a specific binary. See the extension’s README for the full list of settings.

Using only some features

The formatter, linter, and language features share one server but can be turned off independently, so you can adopt just the parts you want:

  • badness.formatting.enable — use Badness as a formatter.
  • badness.diagnostics.enable — show Badness diagnostics (the linter).
  • badness.languageFeatures.enable — hover, completion, navigation, symbols, rename, code actions, and the rest.

All three default to true. They are client-side gates, so the server keeps running and the toggles take effect without a reinstall. For a formatter-only setup, turn off the other two:

{
  "badness.diagnostics.enable": false,
  "badness.languageFeatures.enable": false
}

Turning off badness.diagnostics.enable this way suppresses every diagnostic, including the syntax/parse errors that a badness.toml [lint] selection cannot silence. The badness.toml route stays the right tool when you want to keep parse errors but mute specific lint rules across every editor and the CLI.

Using with LaTeX Workshop

Badness works alongside LaTeX Workshop rather than replacing it. The two divide cleanly: LaTeX Workshop handles building, PDF preview, and SyncTeX, while badness handles formatting, linting, and navigation. Run both, and let each own its half.

Formatting. The badness extension registers itself as the default formatter for LaTeX files. LaTeX Workshop’s own formatter integration is disabled by default (latex-workshop.formatting.latex is "none"); leave it that way so there is a single formatting authority. For BibTeX files, LaTeX Workshop ships a built-in formatter, so pick badness explicitly:

{
  "[bibtex]": {
    "editor.defaultFormatter": "jolars.badness"
  }
}

Linting. LaTeX Workshop’s ChkTeX and lacheck integrations are disabled by default (latex-workshop.linting.chktex.enabled and latex-workshop.linting.lacheck.enabled). Leave them off; enabling them alongside badness produces overlapping diagnostics for many common issues.

Completion. Both extensions contribute completion items, so you may see duplicate suggestions for commands, environments, or citations. This is harmless, but if it bothers you, the latex-workshop.intellisense.* settings let you turn off the overlapping parts on the LaTeX Workshop side.

Other Editors

Any LSP-capable editor can run badness: configure a server whose command is badness lsp, communicating over stdio, for LaTeX documents. Consult your editor’s LSP client documentation for the exact configuration shape.