Skip to content

feat(languages): Assembly support via asm-lsp#2296

Open
sinelaw wants to merge 6 commits into
masterfrom
claude/dazzling-dirac-my5n8n
Open

feat(languages): Assembly support via asm-lsp#2296
sinelaw wants to merge 6 commits into
masterfrom
claude/dazzling-dirac-my5n8n

Conversation

@sinelaw

@sinelaw sinelaw commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Closes #1964

Assembly files previously opened as plain text (.asm) or were mis-highlighted as R (.s/.S — syntect's built-in R grammar claims those extensions), and no language server was wired for assembly.

What's in here

  • New Assembly sublime-syntax grammar (hand-written, ~120 lines) covering the dialects asm-lsp serves: GAS/AT&T and Intel-style NASM/MASM across x86, x86_64, ARM and RISC-V. Mnemonics are highlighted positionally (first word of an instruction line) rather than enumerating every ISA, keeping the grammar small and architecture-agnostic. Registered in types.rs and the build-time syntax packdump.

  • Two language entries sharing the one grammar, mirroring how Verilog/SystemVerilog share svls:

    • asm (.asm/.nasm) — Intel/NASM, ; comments
    • gas (.s/.S) — GAS/AT&T, # comments

    The split exists so comment toggling produces valid code in each dialect (; is a statement separator, not a comment, in x86 GAS).

  • asm-lsp wired as the default LSP for both entries — opt-in per project (auto_start: false) like the other niche servers, root markers .asm-lsp.toml / .git.

  • asm-lsp.ts helper plugin (modeled on nim-lsp.ts):

    • When the server binary is missing, the status-bar click shows install instructions (cargo install asm-lsp / cargo binstall asm-lsp) and a disable action.
    • Dialect config offer: asm-lsp ignores the LSP languageId and defaults to GAS/x86-64, so NASM/MASM/ARM/RISC-V files get bogus diagnostics without a config (true in every editor today). When an assembly buffer opens and no project or user-global .asm-lsp.toml exists, the plugin guesses the assembler and instruction set from the buffer contents and offers to generate the project config — written only after the user picks an option, with alternates, "not now", and a persisted per-project "don't ask again". Accepting restarts the Assembly LSP so the dialect applies immediately. For Intel-syntax dialects the generated config also disables asm-lsp's default diagnostics (they shell out to gcc/clang, which only assemble GAS syntax).

Testing

  • test_highlight_asm_nasm / test_highlight_asm_gas coverage tests with new hello.asm / hello.s fixtures — fail without the grammar.
  • test_asm_language_detection pins .asmasm and .sgas, so the config override keeps beating syntect's R grammar on the s extension (this is the reproducer for the prior misdetection).
  • asm_lsp_config e2e tests drive the config-offer popup against the real plugin: dialect detection for NASM and GAS fixtures, the generated config contents, and no offer when a config already exists.
  • Manually validated in tmux with asm-lsp v0.10.1 installed: highlighting, comment toggling per dialect, server start, hover docs (RDI/mov), completions (pupush...), and the config offer end-to-end — accepting it writes .asm-lsp.toml, restarts the server, and the bogus GAS diagnostics on NASM files disappear.
  • cargo fmt, cargo clippy --all-targets (no new warnings), plugin type-check on asm-lsp.ts passes.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6

@sinelaw

sinelaw commented Jun 14, 2026

Copy link
Copy Markdown
Owner Author

When using LSP hover on a nasm file (included hello.asm) I get

2026-06-14 18:32:05.209 WARN fresh::services::lsp::async_handler: LSP response error from 'asm-lsp' (asm): No information available (code -32803)

claude added 6 commits June 15, 2026 14:25
Assembly files previously opened as plain text (.asm) or were
mis-highlighted as R (.s/.S — syntect's R grammar claims those
extensions). There was also no language server wired for assembly.

Add a hand-written generic Assembly sublime-syntax grammar that covers
the dialects asm-lsp serves: GAS/AT&T and Intel-style NASM/MASM across
x86, x86_64, ARM and RISC-V. Mnemonics are highlighted positionally
(first word of an instruction line) instead of enumerating every ISA,
keeping the grammar small and architecture-agnostic.

Assembly is split into two language entries sharing the one grammar so
comment toggling matches the dialect — NASM/MASM (`.asm`/`.nasm`) uses
`;` while GAS (`.s`/`.S`) uses `#` — mirroring how Verilog and
SystemVerilog share svls. Both entries route to asm-lsp (opt-in per
project like the other niche servers, root markers `.asm-lsp.toml` /
`.git`), and an asm-lsp helper plugin shows install instructions when
the server binary is missing, following the nim-lsp plugin.

Coverage tests pin highlighting for both dialects, and a detection test
pins `.asm`→asm / `.s`→gas so the config override keeps beating
syntect's R grammar on the `s` extension.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
test_settings_paste asserted that the Edit Value dialog shows "astro",
which only held while astro happened to sort first in the languages
map. Adding the asm language entry made "asm" the new first entry and
broke the test on every platform.

Assert that the Edit Value dialog opened instead — the test exercises
pasting into a settings text input, not which language sorts first —
so the next language addition can't break it again.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
asm-lsp ignores the LSP languageId and has no init-time dialect option:
without a project or global .asm-lsp.toml it assumes GAS syntax on
x86/x86-64, so NASM/MASM/ARM/RISC-V files get bogus diagnostics and
wrong docs (this matches every other editor today — none can pass the
dialect over LSP).

Close that gap editor-side: when an assembly buffer opens and no
project or user-global config exists, the asm-lsp helper plugin guesses
the assembler (gas for .s/.S; MASM directives vs NASM for .asm) and
instruction set (arm64/riscv mnemonics, else x86/x86-64) from the
buffer, and offers to create the project .asm-lsp.toml — written only
after the user picks an option, with alternates, "not now", and a
persisted per-project "don't ask again". Accepting restarts the
Assembly LSP so the dialect applies immediately.

For Intel-syntax dialects the generated config also disables asm-lsp's
default diagnostics: those shell out to gcc/clang, which only assemble
GAS syntax, producing the exact junk errors the config exists to avoid.
A comment in the file points at `compiler` to re-enable them.

The e2e tests drive the offer popup against the real plugin: dialect
detection for NASM and GAS fixtures, the written config contents, and
no offer when a config already exists.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
…apshot

The config-offer e2e tests timed out intermittently in CI (ubuntu and
macos) and locally roughly every other run when the suite ran in
parallel. Breadcrumb tracing showed maybeOfferConfig entering and
bailing on its getBufferInfo() check: the plugin state snapshot is
updated asynchronously, so right after `after_file_open` fires the
buffer (and its detected language) may not be visible to plugins yet.

Gate the offer on the event's own path (the same pattern
csharp_support uses) and keep the snapshot only for the optional
content sniff, with a bounded delay-retry that falls back to an
extension-only dialect guess. Also sweep already-open buffers on
`plugins_loaded` so files opened before the plugin finished loading
(CLI arguments, session restore) still get the offer.

The trio of asm_lsp_config tests now passes 15/15 consecutive runs
(previously hung ~50% of parallel runs); the tests also opt into
RUST_LOG tracing so future hangs dump their screens in CI logs.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
Every textDocument/diagnostic request to asm-lsp stalled for the full
30s timeout and got cancelled, repeating on each edit. asm-lsp
(<=0.10.x) advertises `diagnosticProvider` but its
DocumentDiagnosticRequest handler discards the request id and only
emits publishDiagnostics notifications (or nothing when diagnostics
are disabled), so a pull response can never arrive.

Set except_features = [diagnostics] on the default asm-lsp configs so
the pull path is never taken. Pushed diagnostics don't go through the
feature filter and keep working: asm-lsp publishes them on save
(verified: a bogus GAS instruction gets its gutter marker on Ctrl+S,
and the log shows zero diagnostic requests or timeouts).

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
Hovering a non-opcode token (label, %define constant, comment) in an
assembly buffer spammed a WARN: asm-lsp answers hover with the error
`-32803 RequestFailed "No information available"` instead of the
spec-preferred null result. Nothing is broken — hover still works on
opcodes/registers/directives — but fresh logs every LSP error response
at WARN so real protocol mismatches stay visible, so the benign "no
docs here" surfaced as noise on every such hover.

Make the error log method-aware: a RequestFailed (-32803) from an
informational request (hover, completion, signatureHelp, definition,
references, documentSymbol, ...) is a routine "nothing at this
position" and is logged at debug. The same code from an actionable
method (formatting, rename, ...) still warns, since there a failure is
real. The request method is threaded through the pending-request map so
the response dispatcher can classify the error.

https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6
@sinelaw sinelaw force-pushed the claude/dazzling-dirac-my5n8n branch from 735a96d to 07d71a5 Compare June 15, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: ASM (Assembly) file support via asm-lsp

2 participants