feat(languages): Assembly support via asm-lsp#2296
Open
sinelaw wants to merge 6 commits into
Open
Conversation
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) |
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
735a96d to
07d71a5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Assemblysublime-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 intypes.rsand the build-time syntax packdump.Two language entries sharing the one grammar, mirroring how Verilog/SystemVerilog share svls:
asm(.asm/.nasm) — Intel/NASM,;commentsgas(.s/.S) — GAS/AT&T,#commentsThe 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.tshelper plugin (modeled onnim-lsp.ts):cargo install asm-lsp/cargo binstall asm-lsp) and a disable action..asm-lsp.tomlexists, 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_gascoverage tests with newhello.asm/hello.sfixtures — fail without the grammar.test_asm_language_detectionpins.asm→asmand.s→gas, so the config override keeps beating syntect's R grammar on thesextension (this is the reproducer for the prior misdetection).asm_lsp_confige2e 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.RDI/mov), completions (pu→push...), 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 onasm-lsp.tspasses.https://claude.ai/code/session_01A3AGNQNhKCT4489AWthWq6