feat(lsp): offer and document the registered extended mnemonics

Completion merges the extension registry's mnemonics beside the toolchain
entries, with operand shapes measured against the layer's own encoder, and
hover documents a registered mnemonic from its metadata: the extension
notice, the forms, the features, the fixed encodings and the manual
references.

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:06:05 +02:00
1 parent a90ec84bee
commit dadeda144a
3 files changed
+644

No files matched your search

+348
View File
@@ -0,0 +1,348 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
// The language-server side of the extended-instruction layer. The registry
// in asm carries the mnemonics the generated architecture tables do not know;
// this file lets completion and hover document those mnemonics from the
// layer's own metadata, and lets diagnostics leave the layer's verdicts to
// it. Nothing the server states about a registered mnemonic is invented:
// the summaries, forms, features, encodings and references are read from the
// metadata, and the operand spellings are measured against the layer's own
// encoder, so the editor surface can never promise a spelling the assembler
// would refuse.
package lsp
import (
"fmt"
"slices"
"strings"
"sync"
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
)
// extFormDoc is the rendering of one registered form: everything hover and
// completion state about it.
type extFormDoc struct {
summary string // the metadata's one-line description ("" when absent)
form string // the operand shape's label, from ExtForm.String
shape string // the operand spellings the layer's encoder accepts
feature string // the architecture feature, from ExtFeature
word uint32 // the fixed encoding bits
ref string // the manual entry the encoding is transcribed from
}
// extDocsKey addresses one mnemonic's renderings for one architecture.
type extDocsKey struct {
a arch.Arch
name string
}
// extDocsCache memoises the per-form renderings: the probe walks the layer's
// encoder once per mnemonic and architecture, and every later completion or
// hover reuses the answer.
var extDocsCache sync.Map // map[extDocsKey][]extFormDoc
// The probe candidates. They cover every operand kind the layer defines
// today; a kind arriving later joins through its case in extBuild, and an
// unknown kind degrades to a bare label rather than a wrong spelling.
var extProbeArrangements = []arch.ExtArrangement{
arch.ExtArrB, arch.ExtArrH, arch.ExtArrS, arch.ExtArrD, arch.ExtArrQ, arch.ExtArrNone,
}
var extProbeQualifiers = []arch.ExtQualifier{
arch.ExtQualMerging, arch.ExtQualZeroing, arch.ExtQualNone,
}
var extProbeImmediates = []int64{1, 0, 255, -1}
// extForms returns the renderings of one mnemonic's registered forms on a,
// computing and memoising them on first use. It returns nil when the layer
// registers nothing for the mnemonic.
func extForms(a arch.Arch, mnemonic string) []extFormDoc {
key := extDocsKey{a: a, name: strings.ToUpper(mnemonic)}
if v, ok := extDocsCache.Load(key); ok {
docs, _ := v.([]extFormDoc)
return docs
}
var docs []extFormDoc
if cands, ok := asm.LookupExtension(a, mnemonic); ok {
docs = make([]extFormDoc, 0, len(cands))
for _, in := range cands {
docs = append(docs, extFormDoc{
summary: in.Summary,
form: in.Form.String(),
shape: extFormShape(in),
feature: string(in.Feature),
word: in.Word,
ref: in.Ref,
})
}
}
extDocsCache.Store(key, docs)
return docs
}
// extBuild builds one candidate operand list for the form: every vector
// position at reg with the arrangement arr, every predicate position at reg
// with the qualifier qual, every immediate at imm.
func extBuild(in arch.ExtInstr, reg int, arr arch.ExtArrangement, qual arch.ExtQualifier, imm int64) []arch.ExtOperand {
kinds := in.Form.Kinds()
ops := make([]arch.ExtOperand, len(kinds))
for i, k := range kinds {
switch k {
case arch.ExtZReg:
ops[i] = arch.ExtVector(reg, arr)
case arch.ExtPReg:
ops[i] = arch.ExtPredicate(reg, qual)
default: // arch.ExtImm and anything the probe does not model
ops[i] = arch.ExtImmediate(imm)
}
}
return ops
}
// extEncodes reports whether the form's encoder accepts the operand list.
func extEncodes(in arch.ExtInstr, ops []arch.ExtOperand) bool {
_, err := in.Encode(ops)
return err == nil
}
// extAnchor finds one operand list the form encodes, to anchor the probes on;
// ok is false when no probe combination encodes, and the shape then renders
// from the operand kinds alone.
func extAnchor(in arch.ExtInstr) (base []arch.ExtOperand, arr arch.ExtArrangement, qual arch.ExtQualifier, imm int64, ok bool) {
for _, a := range extProbeArrangements {
for _, q := range extProbeQualifiers {
for _, v := range extProbeImmediates {
ops := extBuild(in, 0, a, q, v)
if extEncodes(in, ops) {
return ops, a, q, v, true
}
}
}
}
return nil, arch.ExtArrNone, arch.ExtQualNone, 0, false
}
// extAcceptedArrangements returns the arrangements the form's vector
// positions accept, probed together: the encoder requires the vector
// positions of a form to agree, so one position alone cannot carry the probe.
func extAcceptedArrangements(in arch.ExtInstr, qual arch.ExtQualifier, imm int64) []arch.ExtArrangement {
var out []arch.ExtArrangement
for _, a := range extProbeArrangements {
if extEncodes(in, extBuild(in, 0, a, qual, imm)) {
out = append(out, a)
}
}
return out
}
// extAcceptedQualifiers returns the qualifiers the form's predicate
// positions accept, probed together for the same reason.
func extAcceptedQualifiers(in arch.ExtInstr, arr arch.ExtArrangement, imm int64) []arch.ExtQualifier {
var out []arch.ExtQualifier
for _, q := range extProbeQualifiers {
if extEncodes(in, extBuild(in, 0, arr, q, imm)) {
out = append(out, q)
}
}
return out
}
// extRegRange walks the register numbers one operand position accepts and
// returns a "Z0-Z31" style span when the accepted set is one contiguous run
// from its low end. ok is false when no register encodes there or the set
// is not contiguous, and the caller falls back to a bare label.
func extRegRange(in arch.ExtInstr, base []arch.ExtOperand, pos int, letter string) (span string, ok bool) {
const limit = 32 // both register files the layer defines sit inside 32
lo, hi, n := -1, -1, 0
for r := range limit {
ops := slices.Clone(base)
ops[pos].Reg = r
if !extEncodes(in, ops) {
continue
}
n++
if lo < 0 {
lo = r
}
hi = r
}
if n == 0 || hi-lo+1 != n {
return "", false
}
return fmt.Sprintf("%s%d-%s%d", letter, lo, letter, hi), true
}
// extFormShape measures the operand spellings the form accepts: the register
// spans, the arrangement suffixes and the predicate qualifiers its own
// encoder takes, rendered the way the source writes them. Where a probe
// finds nothing to anchor on, the shape degrades to the operand kinds and
// states nothing the encoder has not proven.
func extFormShape(in arch.ExtInstr) string {
kinds := in.Form.Kinds()
if len(kinds) == 0 {
return ""
}
base, arr, qual, imm, anchored := extAnchor(in)
// The arrangement suffixes and the qualifiers, rendered in enum order.
suffix := ""
quals := []arch.ExtQualifier{qual}
if anchored {
var spelled []string
for _, a := range extAcceptedArrangements(in, qual, imm) {
if a != arch.ExtArrNone {
spelled = append(spelled, a.String())
}
}
if len(spelled) > 0 {
suffix = strings.Join(spelled, "/")
}
quals = extAcceptedQualifiers(in, arr, imm)
}
var parts []string
for i, k := range kinds {
switch k {
case arch.ExtZReg:
label := "Z<n>"
if anchored {
if span, ok := extRegRange(in, base, i, "Z"); ok {
label = span
}
}
parts = append(parts, label+suffix)
case arch.ExtPReg:
label := "P<n>"
if anchored {
if span, ok := extRegRange(in, base, i, "P"); ok {
label = span
}
}
var spelled []string
for _, q := range quals {
if q != arch.ExtQualNone {
spelled = append(spelled, q.String())
}
}
if len(spelled) > 0 {
label += strings.Join(spelled, " or ")
}
parts = append(parts, label)
default:
parts = append(parts, "$imm")
}
}
return strings.Join(parts, ", ")
}
// extFeatureList names the architecture features the forms belong to, in
// first-occurrence order, "" when the metadata carries none.
func extFeatureList(docs []extFormDoc) string {
var out []string
for _, d := range docs {
if d.feature != "" && !slices.Contains(out, d.feature) {
out = append(out, d.feature)
}
}
return strings.Join(out, ", ")
}
// renderExtensionHover renders the hover documentation of a registered
// extended mnemonic: the fact that it is an extension above the toolchain,
// then one entry per registered form. A form with no summary in the
// metadata states the encoding facts alone: the shape label, the feature,
// the fixed encoding bits and the manual reference, and nothing more.
func renderExtensionHover(mnemonic string, docs []extFormDoc) string {
if len(docs) == 0 {
return ""
}
var b strings.Builder
b.WriteString("**" + mnemonic + "**: extension above the Go toolchain")
if f := extFeatureList(docs); f != "" {
b.WriteString(" (" + f + ")")
}
for _, d := range docs {
b.WriteString("\n\n- ")
// The summary leads where the metadata carries one; without it the
// form's own label stands in, and the bullet states the encoding
// facts and nothing more.
lead := d.summary
if lead == "" {
lead = d.form
}
if lead != "" {
b.WriteString(lead + ": ")
}
b.WriteString(d.shape)
var clauses []string
if d.summary != "" && d.form != "" {
clauses = append(clauses, d.form)
}
if d.feature != "" {
clauses = append(clauses, d.feature)
}
if d.word != 0 {
clauses = append(clauses, fmt.Sprintf("encoding 0x%08x", d.word))
}
if len(clauses) > 0 {
b.WriteString(" (" + strings.Join(clauses, ", ") + ")")
}
if d.ref != "" {
b.WriteString("\n " + d.ref)
}
}
return b.String()
}
// extensionHover returns the hover documentation of a registered extended
// mnemonic, "" when the layer registers nothing for it.
func extensionHover(a arch.Arch, word string) string {
return renderExtensionHover(strings.ToUpper(word), extForms(a, word))
}
// applyExtensionCompletion merges the registered extended mnemonics into the
// completion list, so they are offered exactly where the toolchain
// mnemonics are: a mnemonic the base table also knows enriches its existing
// item, a registry-only one gains its own. The detail carries the operand
// shapes the layer's encoder accepts.
func applyExtensionCompletion(items []CompletionItem, a arch.Arch) []CompletionItem {
byLabel := make(map[string]int, len(items))
for i, it := range items {
if _, ok := byLabel[it.Label]; !ok {
byLabel[it.Label] = i
}
}
for _, name := range asm.ExtensionNames(a) {
docs := extForms(a, name)
if len(docs) == 0 {
continue
}
shapes := make([]string, 0, len(docs))
for _, d := range docs {
shapes = append(shapes, d.shape)
}
hover := extensionHover(a, name)
if i, ok := byLabel[name]; ok {
items[i].Detail += "; extension: " + strings.Join(shapes, "; ")
if items[i].Documentation == "" {
items[i].Documentation = hover
} else {
items[i].Documentation += "\n\n" + hover
}
continue
}
byLabel[name] = len(items)
items = append(items, CompletionItem{
Label: name,
Kind: ciFunction,
Detail: "extension: " + strings.Join(shapes, "; "),
Documentation: hover,
})
}
return items
}
+285
View File
@@ -0,0 +1,285 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lsp
import (
"encoding/json"
"strings"
"testing"
"sourcedock.dev/petrbalvin/gasm-sdk/arch"
"sourcedock.dev/petrbalvin/gasm-sdk/asm"
)
// sveDoc is an arm64 kernel over the registered extended-instruction layer:
// every SVE form of ADD, a registry-only mnemonic, the signed-immediate MUL,
// and one mnemonic no layer registers, which must stay reported wherever the
// registry is silent.
const sveDoc = "#include \"textflag.h\"\n" +
"TEXT \u00b7f(SB), NOSPLIT, $0\n" +
"\tADD Z0.S, Z1.S, Z2.S\n" +
"\tADD Z1.S, P0/M, Z0.S\n" +
"\tADD $255, Z0.S\n" +
"\tSQADD Z3.S, Z4.S, Z5.S\n" +
"\tMUL $-128, Z0.B\n" +
"\tNOSUCHTHING Z0.S, Z1.S\n" +
"\tRET\n"
// completionItems runs one completion request and indexes the answers by
// label. The base surface predates this change and carries a few duplicate
// labels (a directive or pseudo-register can also sit in the generated
// tables); callers that care about a specific label count it in the slice.
func completionItems(t *testing.T, uri, text string, id int) ([]CompletionItem, map[string]CompletionItem) {
t.Helper()
in := session(uri, text) +
frame(id, "textDocument/completion", map[string]any{
"textDocument": map[string]any{"uri": uri},
"position": map[string]any{"line": 2, "character": 1},
}) + frame(nil, "exit", nil)
msgs := run(t, in)
resp := findByID(msgs, id)
if resp == nil {
t.Fatal("no completion response")
}
var items []CompletionItem
if err := json.Unmarshal(mustResult(t, resp), &items); err != nil {
t.Fatal(err)
}
out := make(map[string]CompletionItem, len(items))
for _, it := range items {
if _, dup := out[it.Label]; !dup {
out[it.Label] = it
}
}
return items, out
}
// countLabel counts the items carrying one label.
func countLabel(items []CompletionItem, label string) int {
n := 0
for _, it := range items {
if it.Label == label {
n++
}
}
return n
}
// hoverAt runs one hover request and returns the rendered markdown.
func hoverAt(t *testing.T, uri, text string, id, line, char int) *Hover {
t.Helper()
in := session(uri, text) +
frame(id, "textDocument/hover", map[string]any{
"textDocument": map[string]any{"uri": uri},
"position": map[string]any{"line": line, "character": char},
}) + frame(nil, "exit", nil)
msgs := run(t, in)
resp := findByID(msgs, id)
if resp == nil {
t.Fatal("no hover response")
}
var h *Hover
if err := json.Unmarshal(mustResult(t, resp), &h); err != nil {
t.Fatal(err)
}
return h
}
// publishedDiagnostics runs one didOpen and returns the pushed diagnostics.
func publishedDiagnostics(t *testing.T, uri, text string) []Diagnostic {
t.Helper()
msgs := run(t, session(uri, text)+frame(nil, "exit", nil))
pub := findMethod(msgs, "textDocument/publishDiagnostics")
if pub == nil {
t.Fatal("no publishDiagnostics notification")
}
var p publishDiagnosticsParams
json.Unmarshal(pub.Params, &p)
return p.Diagnostics
}
// TestCompletionExtensionMnemonicsARM64 checks that the registered extended
// mnemonics are offered where the toolchain mnemonics are, with the operand
// shapes the registry accepts, and that a mnemonic both layers know merges
// into one item instead of two.
func TestCompletionExtensionMnemonicsARM64(t *testing.T) {
items, byLabel := completionItems(t, "file:///v_arm64.s", sveDoc, 40)
sqadd, ok := byLabel["SQADD"]
if !ok {
t.Fatal("completion missing the registry mnemonic SQADD")
}
if sqadd.Kind != ciFunction {
t.Errorf("SQADD kind = %d, want the instruction kind", sqadd.Kind)
}
for _, want := range []string{"extension: ", "Z0-Z31.B/.H/.S/.D", "$imm"} {
if !strings.Contains(sqadd.Detail, want) {
t.Errorf("SQADD detail = %q, want it to carry %q", sqadd.Detail, want)
}
}
if !strings.Contains(sqadd.Documentation, "extension above the Go toolchain") {
t.Errorf("SQADD documentation = %q, want the extension notice", sqadd.Documentation)
}
subr, ok := byLabel["SUBR"]
if !ok {
t.Fatal("completion missing the registry mnemonic SUBR")
}
if !strings.Contains(subr.Detail, "P0-P7/M") {
t.Errorf("SUBR detail = %q, want the predicate spelling P0-P7/M", subr.Detail)
}
// ADD sits in both layers: one item, the table entry carrying the
// extension shapes beside its own summary.
if n := countLabel(items, "ADD"); n != 1 {
t.Errorf("ADD offered %d times, want one merged item", n)
}
add := byLabel["ADD"]
if !strings.Contains(add.Detail, "64-bit") || !strings.Contains(add.Detail, "extension: ") {
t.Errorf("ADD detail = %q, want the merged table and extension text", add.Detail)
}
if !strings.Contains(add.Documentation, "extension above the Go toolchain") {
t.Errorf("ADD documentation = %q, want the extension notice beside the table summary", add.Documentation)
}
// The toolchain surface is untouched beside the additions.
for _, want := range []string{"TEXT", "MOVD", "NOSPLIT"} {
if _, ok := byLabel[want]; !ok {
t.Errorf("completion lost the toolchain entry %q", want)
}
}
// No extended mnemonic arrives as a second item beside its merge.
for _, name := range asm.ExtensionNames(arch.ARM64) {
if n := countLabel(items, name); n != 1 {
t.Errorf("extended mnemonic %q offered %d times, want once", name, n)
}
}
}
// TestCompletionNoExtensionMnemonicsAMD64 is the per-architecture negative
// case: the extension layer registers nothing on amd64, so no extended
// mnemonic may be invented into the list and the toolchain surface stands.
func TestCompletionNoExtensionMnemonicsAMD64(t *testing.T) {
_, byLabel := completionItems(t, "file:///v_amd64.s", sveDoc, 41)
for _, banned := range []string{"SQADD", "SUBR", "UQADD", "UQSUB", "SMULH"} {
if _, ok := byLabel[banned]; ok {
t.Errorf("completion invented %q on amd64, whose layer registers nothing", banned)
}
}
if _, ok := byLabel["MOVQ"]; !ok {
t.Error("completion lost MOVQ on amd64")
}
if add, ok := byLabel["ADD"]; ok && strings.Contains(add.Detail, "extension: ") {
t.Errorf("ADD detail = %q, want no extension text where the layer is absent", add.Detail)
}
}
// TestHoverExtensionMnemonicARM64 checks that a registered extended mnemonic
// documents itself from the layer's metadata: the extension notice, the
// family, the operand shapes, the fixed encoding and the manual reference.
func TestHoverExtensionMnemonicARM64(t *testing.T) {
h := hoverAt(t, "file:///v_arm64.s", sveDoc, 42, 5, 3) // on SQADD
if h == nil {
t.Fatal("hover over SQADD returned nothing")
}
for _, want := range []string{
"**SQADD**: extension above the Go toolchain (sve)",
"Add signed saturating scalable vector elements, unpredicated: Z0-Z31.B/.H/.S/.D, Z0-Z31.B/.H/.S/.D, Z0-Z31.B/.H/.S/.D",
"unpredicated vectors, sve, encoding 0x04201000",
"$imm, Z0-Z31.B/.H/.S/.D",
"DDI 0487J",
} {
if !strings.Contains(h.Contents.Value, want) {
t.Errorf("hover = %q, want it to carry %q", h.Contents.Value, want)
}
}
if strings.Contains(h.Contents.Value, ".Q") {
t.Errorf("hover = %q, states an arrangement the layer's encoder refuses", h.Contents.Value)
}
}
// TestHoverSharedMnemonicARM64 checks the merge on a mnemonic both layers
// know: the table entry keeps its summary and the registered forms attach
// beside it.
func TestHoverSharedMnemonicARM64(t *testing.T) {
h := hoverAt(t, "file:///v_arm64.s", sveDoc, 43, 2, 3) // on ADD
if h == nil {
t.Fatal("hover over ADD returned nothing")
}
if !strings.Contains(h.Contents.Value, "**ADD**: ADD (64-bit)") {
t.Errorf("hover = %q, want the generated table summary", h.Contents.Value)
}
if !strings.Contains(h.Contents.Value, "extension above the Go toolchain") {
t.Errorf("hover = %q, want the registered forms beside the summary", h.Contents.Value)
}
if !strings.Contains(h.Contents.Value, "P0-P7/M") {
t.Errorf("hover = %q, want the predicated form's spelling", h.Contents.Value)
}
}
// TestHoverNoExtensionMnemonicAMD64 is the per-architecture negative case:
// with no registered layer on amd64, a hover over a registry-only mnemonic
// answers null exactly as before.
func TestHoverNoExtensionMnemonicAMD64(t *testing.T) {
if h := hoverAt(t, "file:///v_amd64.s", sveDoc, 44, 5, 3); h != nil {
t.Errorf("hover over SQADD on amd64 = %q, want null", h.Contents.Value)
}
}
// TestExtensionShapesMatchRegistry pins the measuring: the rendered shapes
// are exactly the spellings the registry's encoder accepts, checked by
// round-tripping the boundary spellings through asm.EncodeExtension.
func TestExtensionShapesMatchRegistry(t *testing.T) {
docs := extForms(arch.ARM64, "ADD")
if len(docs) != 3 {
t.Fatalf("ADD renders %d forms, want 3", len(docs))
}
wantShapes := []string{
"Z0-Z31.B/.H/.S/.D, Z0-Z31.B/.H/.S/.D, Z0-Z31.B/.H/.S/.D",
"Z0-Z31.B/.H/.S/.D, P0-P7/M, Z0-Z31.B/.H/.S/.D",
"$imm, Z0-Z31.B/.H/.S/.D",
}
for i, want := range wantShapes {
if docs[i].shape != want {
t.Errorf("form %d shape = %q, want %q", i, docs[i].shape, want)
}
}
// The predicate span ends where the encoder's three-bit field ends.
if _, err := asm.EncodeExtension(arch.ARM64, "ADD",
arch.ExtVector(0, arch.ExtArrS), arch.ExtPredicate(7, arch.ExtQualMerging), arch.ExtVector(1, arch.ExtArrS)); err != nil {
t.Errorf("P7/M refused: %v", err)
}
if _, err := asm.EncodeExtension(arch.ARM64, "ADD",
arch.ExtVector(0, arch.ExtArrS), arch.ExtPredicate(8, arch.ExtQualMerging), arch.ExtVector(1, arch.ExtArrS)); err == nil {
t.Error("P8/M encoded, the span overstates the registry")
}
// The arrangement suffixes stop at .D: .Q refuses while the shape omits it.
if _, err := asm.EncodeExtension(arch.ARM64, "ADD",
arch.ExtVector(0, arch.ExtArrQ), arch.ExtVector(1, arch.ExtArrQ), arch.ExtVector(2, arch.ExtArrQ)); err == nil {
t.Error(".Q encoded, the suffixes overstate the registry")
}
// And .B, which the shape spells, encodes.
if _, err := asm.EncodeExtension(arch.ARM64, "ADD",
arch.ExtVector(0, arch.ExtArrB), arch.ExtVector(1, arch.ExtArrB), arch.ExtVector(2, arch.ExtArrB)); err != nil {
t.Errorf(".B refused: %v", err)
}
}
// TestExtensionHoverWithoutProse checks the no-prose contract: a form whose
// metadata carries no summary states the encoding facts it has and nothing
// invented in their place.
func TestExtensionHoverWithoutProse(t *testing.T) {
docs := []extFormDoc{
{form: "unsigned immediate", shape: "$imm, Z0-Z31.B/.H/.S/.D", feature: "sve", word: 0x2520c000},
}
md := renderExtensionHover("TESTMNEM", docs)
for _, want := range []string{
"**TESTMNEM**: extension above the Go toolchain (sve)",
"- unsigned immediate: $imm, Z0-Z31.B/.H/.S/.D (sve, encoding 0x2520c000)",
} {
if !strings.Contains(md, want) {
t.Errorf("hover = %q, want it to carry %q", md, want)
}
}
}
+11
View File
@@ -114,6 +114,9 @@ func (s *Server) completion(p completionParams) []CompletionItem {
}
items = append(items, CompletionItem{Label: r.Name, Kind: kind, Detail: r.Desc})
}
// The extended-instruction layer, beside the toolchain entries: the
// registered mnemonics with the operand shapes the registry accepts.
items = applyExtensionCompletion(items, a.Arch)
// Local labels defined in the document.
if f, _ := parser.Parse("", s.docs[p.TextDocument.URI]); f != nil {
for _, name := range labelNames(f) {
@@ -134,6 +137,7 @@ func (s *Server) hover(p hoverParams) *Hover {
a := arch.ForArch(arch.FromFilename(uriPath(p.TextDocument.URI)))
var md string
ext := extensionHover(a.Arch, word)
// Directives first: several of their names (TEXT, PCALIGN, FUNCDATA,
// the BYTE family) also sit in the generated instruction tables with
// empty summaries, and the directive documentation is the answer a
@@ -142,6 +146,13 @@ func (s *Server) hover(p hoverParams) *Hover {
md = doc
} else if in, ok := a.Lookup(word); ok {
md = "**" + in.Name + "**: " + in.Summary
// A mnemonic the extension layer registers documents its registered
// forms beside the table entry.
if ext != "" {
md += "\n\n" + ext
}
} else if ext != "" {
md = ext
} else if r, ok := a.Register(word); ok {
md = "**" + r.Name + "**: " + r.Class.String() + " register. " + r.Desc
} else if desc, ok := arch.PseudoRegDesc(word); ok {