Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
199 commits
Select commit Hold shift + click to select a range
7709e72
fix(site) Highlight Java properties examples
tony Oct 15, 2026
eec6112
fix(api-model) Resolve modular JDK references
tony Oct 15, 2026
8b7e229
fix(site) Follow sidebar ancestry in breadcrumbs
tony Oct 15, 2026
ab56a15
fix(site) Link Scala resource-management calls
tony Oct 15, 2026
ec33a23
fix(site) Highlight reference preview signatures
tony Oct 15, 2026
81a01dd
fix(site) Keep breadcrumb schema destinations valid
tony Oct 15, 2026
326dd64
fix(site) Keep API sections below the site header
tony Oct 15, 2026
6e46571
perf(site) Scope inventory construction by language
tony Oct 15, 2026
b17ddd0
test(site) Type preview endpoint fixture context
tony Oct 15, 2026
0a7a5ce
test(site) Type browser layout observers
tony Oct 15, 2026
66a52c9
fix(site) Preserve product names in overview breadcrumbs
tony Oct 15, 2026
711fe23
fix(site) Clean up API reference panels and controls
tony Oct 15, 2026
eab66cc
fix(site) Link contextual tmux command mentions
tony Oct 15, 2026
f8e2897
fix(api-model) Preserve native Go doc code blocks
tony Oct 15, 2026
4366bed
fix(api-model) Resolve opaque Scala public bounds
tony Oct 15, 2026
ece7f55
docs(site) Teach native tmux queries
tony Oct 15, 2026
58e0fc6
fix(site) Select native API glossary content by port
tony Oct 15, 2026
fbcdac7
fix(site) Explain Scala opaque type modifiers
tony Oct 15, 2026
a6a37d7
docs(site) Explain native tmux object identity
tony Oct 15, 2026
8000e02
docs(site) Teach native tmux control clients
tony Oct 15, 2026
4bfe7b9
fix(site) Author native glossary terms for every port
tony Oct 15, 2026
47ce417
docs(site) Build native tmux workspace layouts
tony Oct 15, 2026
2aff4ab
test(site) Select owned transport prose
tony Oct 15, 2026
c07c318
docs(site) Scope native tmux section overviews
tony Oct 15, 2026
489bd46
fix(site) Preserve only existing glossary fragments
tony Oct 15, 2026
d6f2c8b
fix(api-model) Register opaque Scala modifiers
tony Oct 15, 2026
ac98a63
test(site) Route native glossary terms by language
tony Oct 15, 2026
c38af2b
docs(site) Send pane input with native tmux
tony Oct 15, 2026
d54746a
docs(site) Own native tmux sockets and cleanup
tony Oct 15, 2026
6e60be8
fix(site) Explain native named types and constructors
tony Oct 15, 2026
6d2c72c
docs(site) Read native tmux fields and environments
tony Oct 15, 2026
f8c0857
fix(site) Draw one themed cue on inline references
tony Oct 15, 2026
708e88a
docs(site) Use native tmux options and pane I/O
tony Oct 15, 2026
ccb3f53
docs(site) Query and coordinate native tmux state
tony Oct 15, 2026
3c2559f
docs(site) Explain native tmux queues and failures
tony Oct 15, 2026
3e466d6
test(site) Guard native tmux prose destinations
tony Oct 15, 2026
263aa52
docs(site) Cite the resolved native tmux manual commit
tony Oct 15, 2026
a37c897
fix(site) Resolve native manual source pins to commits
tony Oct 15, 2026
b605bc6
test(site) Mirror owned core reference endpoints
tony Oct 15, 2026
3ae4026
test(api-model) Retain corpus ownership after prose moves
tony Oct 15, 2026
2d70fdb
fix(api-model) Preserve embedded reST hyperlinks
tony Oct 15, 2026
999694f
docs(site) Restore native example verification context
tony Oct 15, 2026
e30994c
docs(site) Link native verification tooling to its owner
tony Oct 15, 2026
5669daf
test(site) Use compatible DOM fixture types
tony Oct 15, 2026
837f7f6
test(site) Prepare both initial sample routes
tony Oct 15, 2026
6515d8c
fix(api-model) Honor native reST link boundaries
tony Oct 15, 2026
d294bcf
fix(site) Keep unmeasured API controls in flow
tony Oct 15, 2026
7bd5a89
docs(api) Refresh source-bound Python manual citations
tony Oct 15, 2026
1a10351
perf(site) Skip JSON named export transforms
tony Oct 15, 2026
17dbf05
test(site) Exercise code tabs with owned browser fixtures
tony Oct 15, 2026
d6853ae
docs(api) Bind Python citations to formatted source
tony Oct 15, 2026
79512bc
test(site) Use the fixture anchor type
tony Oct 15, 2026
9eca885
test(site) Verify product breadcrumb ancestry
tony Oct 15, 2026
f58a1ab
docs(tmux) Retain native hook compatibility links
tony Oct 15, 2026
ca37233
docs(tmux) Clarify the compatibility example reference
tony Oct 15, 2026
9fb5d3c
test(site) Reuse verified product index facts
tony Oct 15, 2026
966226c
fix(site) Preserve native signature boundaries
tony Oct 15, 2026
ba8cff8
fix(site) Link generic types in inline documentation
tony Oct 15, 2026
20468f1
test(site) Serve core Markdown in the shared preview
tony Oct 15, 2026
fd256ec
fix(site) Separate preview dots from code frames
tony Oct 15, 2026
89d70ac
fix(site) Allow a fixed browser check port
tony Oct 15, 2026
430c3dd
fix(site) Document TypeScript constant members
tony Oct 15, 2026
0db24ed
fix(api-model) Preserve C# record class identity
tony Oct 15, 2026
36be085
fix(api-model) Retain Rust async function modifiers
tony Oct 15, 2026
c41d907
fix(site) Format TypeScript callable signatures within API limits
tony Oct 15, 2026
2980ed3
test(site) Reproduce native signature audit crash
tony Oct 15, 2026
d02fe5b
fix(site) Bind native declarations in browser audit
tony Oct 15, 2026
b785d6b
test(site) Enforce native browser audit bindings
tony Oct 15, 2026
fc6d92a
fix(site) Preserve fragment arrivals through reflow
tony Oct 15, 2026
8aca055
test(api-model) Cover Java record component loss
tony Oct 15, 2026
e0df2ae
fix(api-model) Recover Java record components
tony Oct 15, 2026
dbd6894
fix(api-model) Keep record accessor identities distinct
tony Oct 15, 2026
33c8ad6
test(api-model) Guard Java record member boundaries
tony Oct 15, 2026
707e3b6
fix(api-model) Preserve explicit record accessor claims
tony Oct 15, 2026
7a83851
fix(api-model) Recognize native record accessors structurally
tony Oct 15, 2026
4368c40
feat(site) Format Scala and Kotlin callable headers
tony Oct 15, 2026
964500f
fix(site) Retain JVM header comment boundaries
tony Oct 15, 2026
e9de90c
test(reference[parameters]) Capture misplaced links
tony Oct 15, 2026
22b7cf6
fix(reference[parameters]) Keep links beside names
tony Oct 15, 2026
6a85017
test(reference[parameters]) Enforce link placement
tony Oct 15, 2026
c4eacb8
fix(site) Refresh native member mention targets
tony Oct 15, 2026
848ed9b
test(site) Bind Julia xref floor to corrected command links
tony Oct 15, 2026
c9c8b39
test(site) Reproduce missing native browser fixtures
tony Oct 15, 2026
83baf5e
fix(site) Include native browser signature fixtures
tony Oct 15, 2026
23871c1
test(site) Enforce native browser fixture coverage
tony Oct 15, 2026
af55f2a
test(site) Reproduce uncontained formatted signatures
tony Oct 16, 2026
348a893
fix(site) Contain formatted declaration overflow
tony Oct 16, 2026
701776b
test(site) Enforce formatted signature containment
tony Oct 16, 2026
5b1f71e
test(site) Reproduce keyword drift on inner scroll
tony Oct 16, 2026
d0baeab
fix(site) Keep keyword help beside scrolled tokens
tony Oct 16, 2026
7628625
test(site) Enforce keyword help scroll placement
tony Oct 16, 2026
e26fe72
test(api) Reproduce missing Go callable source facts
tony Oct 16, 2026
ed6914b
fix(api) Retain Go callable source headers and parameter facts
tony Oct 16, 2026
9fb85d5
test(api) Require native Go callable facts
tony Oct 16, 2026
16a318b
test(site) Reproduce missing native Go signature layouts
tony Oct 16, 2026
d18aa93
test(site) Cover Go native layout boundaries
tony Oct 16, 2026
97ae643
fix(site) Format native Go callable headers at documentation width
tony Oct 16, 2026
26bc39d
test(site) Require Go layout and bundled grammar invariants
tony Oct 16, 2026
6bfc286
test(site) Reproduce Go header comment ownership failures
tony Oct 16, 2026
3cf1fd9
fix(site) Preserve trailing Go header comments
tony Oct 16, 2026
6b72631
test(site) Require Go header comment preservation
tony Oct 16, 2026
52054fb
test(site) Reproduce hosted Scala parameter link rejection
tony Oct 16, 2026
6dc5b48
fix(site) Validate native parameter links in the browser audit
tony Oct 16, 2026
58cd001
test(site) Require owned native parameter destinations
tony Oct 16, 2026
ab89d11
test(api-model) Reproduce missing Rust enum payloads
tony Oct 16, 2026
92fa018
test(site) Reproduce missing Rust payload documentation
tony Oct 16, 2026
edab423
test(api-model) Cover native Rust payload label boundaries
tony Oct 16, 2026
8adebcb
fix(docs) Recover source-backed Rust enum payload fields
tony Oct 16, 2026
9a99b9e
test(docs) Enforce Rust payload fields across reference surfaces
tony Oct 16, 2026
0a90b54
test(api-model) Reproduce Rust callback label links in payloads
tony Oct 16, 2026
d082bac
fix(api-model) Keep Rust payload callback labels unlinked
tony Oct 16, 2026
3efbf49
test(api-model) Enforce callback label scope for Rust payloads
tony Oct 16, 2026
4d84cf9
test(site) Reproduce overlong Go generic results
tony Oct 16, 2026
6af2078
fix(site) Wrap sole Go type arguments when over budget
tony Oct 16, 2026
f6cb834
test(site) Require bounded Go generic result layouts
tony Oct 16, 2026
bf9948d
test(api-model) Reproduce Go type and method collisions
tony Oct 16, 2026
c77a17e
fix(api-model) Restrict Go type lookups to type declarations
tony Oct 16, 2026
a28fc63
test(api-model) Require actual Go type destinations
tony Oct 16, 2026
1fcfc05
test(site) Reproduce Scala inline header geometry rejection
tony Oct 16, 2026
82be182
fix(site) Validate available native signature width
tony Oct 16, 2026
eff6d27
test(site) Require aligned native signature geometry
tony Oct 16, 2026
5243b86
test(api-model) Reproduce missing Rust ID types
tony Oct 16, 2026
2691546
fix(api-model) Recover native Rust ID structs
tony Oct 16, 2026
54a2e11
test(api-model) Guard native Rust ID recovery
tony Oct 16, 2026
687f044
test(api-model) Reproduce payload navigation drift
tony Oct 16, 2026
16ba7f2
fix(api-model) Share member navigation ownership
tony Oct 16, 2026
2ff6ca8
test(api-model) Guard member ownership parity
tony Oct 16, 2026
3afc5f8
fix(docs) Refresh mentions after native member recovery
tony Oct 16, 2026
341a1db
test(api-model) Reproduce missing Java headers
tony Oct 16, 2026
08d7ef5
fix(api-model) Retain native Java callable headers
tony Oct 16, 2026
a4e6ddc
test(api-model) Require native Java source facts
tony Oct 16, 2026
fb1844e
test(site) Reproduce normalized Java callable bars
tony Oct 16, 2026
717a5d5
fix(site) Format source-backed Java callable headers
tony Oct 16, 2026
f28577f
fix(site) Preserve Java varargs type links
tony Oct 16, 2026
29d424e
test(site) Require native Java callable layouts
tony Oct 16, 2026
487abb1
test(java) Reproduce annotated callable extraction edges
tony Oct 16, 2026
573fcf9
fix(java) Preserve annotated callable boundaries
tony Oct 16, 2026
7ac8d45
test(java) Require preserved annotated callable identity
tony Oct 16, 2026
3f8e865
test(site) Reproduce Kotlin header width failures
tony Oct 16, 2026
125b00f
fix(site) Wrap Kotlin defaults and return types
tony Oct 16, 2026
dbfd501
test(site) Require bounded Kotlin header layouts
tony Oct 16, 2026
87c200a
test(site[prose]) capture native tmux ownership failures
tony Oct 16, 2026
de706ae
fix(site[prose]) respect native tmux prose ownership
tony Oct 16, 2026
1242297
test(site[prose]) activate native ownership regressions
tony Oct 16, 2026
0045cde
test(site) Reproduce canonical tmux prose ownership
tony Oct 16, 2026
c174b83
fix(site) Recognize canonical tmux prose routes
tony Oct 16, 2026
f49d440
test(site) Require canonical tmux ownership controls
tony Oct 16, 2026
4913e1e
test(site) Bind Java exports to native headers
tony Oct 16, 2026
f4e324f
test(site[types]) Expose lost native syntax roles
tony Oct 16, 2026
d5a7a38
fix(site[types]) Preserve native nonreference roles
tony Oct 16, 2026
5339895
fix(scripts[types]) Retain nested attribute audit coverage
tony Oct 16, 2026
38c29b4
test(site[types]) Enforce native syntax role regressions
tony Oct 16, 2026
9d01ce9
test(scripts[types]) Expose comment-hidden type names
tony Oct 16, 2026
5581c87
fix(scripts[types]) Strip comments before quoted tags
tony Oct 16, 2026
9a3a79e
test(scripts[types]) Enforce visible text audit controls
tony Oct 16, 2026
fec6e26
test(scripts[types]) Expose HTML token boundaries
tony Oct 16, 2026
2c61107
fix(scripts[types]) Tokenize comments and tags together
tony Oct 16, 2026
fa9c0c1
test(scripts[types]) Enforce visible text boundaries
tony Oct 16, 2026
bd2e9c0
test(site[signatures]) Expose formatter line wrapping
tony Oct 16, 2026
b433fb7
fix(site[signatures]) Preserve formatter-owned rows
tony Oct 16, 2026
4697a3d
test(api) Reproduce overlong Scala parameter types
tony Oct 16, 2026
674275d
fix(api) Wrap overlong Scala parameter types after their colon
tony Oct 16, 2026
d608706
test(api) Verify bounded Scala parameter continuations
tony Oct 16, 2026
81340af
test(api) Reproduce overlong Scala return types
tony Oct 16, 2026
5daebd7
fix(api) Wrap Scala return types after their colon
tony Oct 16, 2026
c2aef1b
test(api) Verify bounded Scala return continuations
tony Oct 16, 2026
d1d9ba8
test(api) Type matched Scala literals explicitly
tony Oct 16, 2026
d9eef58
test(api-model) Reproduce missing Java semantic roles
tony Oct 16, 2026
0d94e0c
test(site) Require verified JVM annotation source links
tony Oct 16, 2026
386a4f3
test(site) Reproduce Java roles in rendered signatures
tony Oct 16, 2026
d03373c
fix(api-model) Preserve Java annotation and type scopes
tony Oct 16, 2026
f8ffe4f
test(site) Enforce native Java type roles
tony Oct 16, 2026
0df187b
test(api-model) Isolate Java overload scope boundaries
tony Oct 16, 2026
0ab8be7
test(api-model) Retain explicit accessor scope facts
tony Oct 16, 2026
4ee6e43
test(api-model) Preserve implicit accessor overload scope
tony Oct 16, 2026
ef80bca
fix(api-model) Bind Java scopes to selected overloads
tony Oct 16, 2026
0b72354
test(api-model) Verify overload and record isolation
tony Oct 16, 2026
a24535b
test(api-model) Reproduce Java owner scope leakage
tony Oct 16, 2026
1e6b83c
test(api-model) Bind Java scope regressions to exact declarations
tony Oct 16, 2026
0cb26d2
fix(api-model) Derive Java owner scopes from grammar
tony Oct 16, 2026
02ecf67
test(api-model) Enforce grammar-owned Java type scopes
tony Oct 16, 2026
b891cf9
test(site) Require the Java annotation glossary definition
tony Oct 16, 2026
4aae375
fix(site) Define Java annotation glossary semantics
tony Oct 16, 2026
1e874ea
test(site) Verify Java annotation glossary exports
tony Oct 16, 2026
2e74015
perf(site) Drop unused Mono italic preload
tony Oct 16, 2026
cd3c4db
test(api) Reproduce missing Rust callable headers
tony Oct 16, 2026
0aa50b7
fix(api) Preserve native Rust callable headers
tony Oct 16, 2026
79d5f4b
test(api) Verify native Rust header fidelity
tony Oct 16, 2026
1350f6f
test(api) Reproduce Rust header scope defects
tony Oct 16, 2026
f85c720
fix(api) Keep Rust header scopes and grammar roles
tony Oct 16, 2026
b84d833
test(api) Verify Rust header scope repairs
tony Oct 16, 2026
b2dc9b5
test(site) Record independently reviewed reference visuals
tony Oct 16, 2026
4160560
fix(site) Align native API signature treatments
tony Oct 16, 2026
f570202
fix(search) Reserve modal space for results
tony Oct 16, 2026
f70a778
fix(site) Allow modal search input to shrink
tony Oct 16, 2026
76195d0
Use the UI font for API breadcrumb labels
tony Oct 16, 2026
a5f542e
Balance leading declaration metadata spacing
tony Oct 16, 2026
d9a1715
Add scroll clearance to modal search results and filters
tony Oct 16, 2026
893daa1
fix(site) Restore compact kind badge borders
tony Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ Chrome:
$ LIBTMUX_DOCS_BROWSER_CHANNEL=chrome pnpm test
```

To keep local browser checks on a fixed port, stop the preview using that
port, then run:

```console
$ LIBTMUX_DOCS_BROWSER_PORT=53971 pnpm test
```

The value must be an integer from 1 to 65535. An occupied explicit port fails
the check instead of selecting another port. This setting also applies to
direct `check-dev.mjs` invocations, including `--serve`; without it, automated
checks select an available port and `--serve` uses 53971. Restart the shared
preview after the checks finish.

Install the pinned Chromium build for local browser checks with
`pnpm --filter @libtmux/site exec playwright install chromium`. CI installs
that build and its system dependencies before the publication audit.
Expand Down
5 changes: 4 additions & 1 deletion packages/api-model/src/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const TYPESCRIPT_UTILITY_TYPES: Record<string, string> = Object.fromEntri
)
const kotlin = (page: string) => `https://kotlinlang.org/api/core/kotlin-stdlib/${page}/`
const scala = (page: string) => `https://www.scala-lang.org/api/3.x/${page}.html`
const scalaUsing = 'https://www.scala-lang.org/api/3.9.0/scala/util/Using$.html'
const fsharp = (page: string) => `https://fsharp.github.io/fsharp-core-docs/reference/${page}.html`
const coroutines = (page: string) => `https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/${page}`
const catsEffect = (name: string) => `https://typelevel.org/cats-effect/api/3.x/cats/effect/kernel/${name}.html`
Expand Down Expand Up @@ -160,7 +161,9 @@ export const BUILTINS: Record<string, Record<string, string>> = {
IterableOnce: scala('scala/collection/IterableOnce'),
'scala.concurrent.duration.FiniteDuration': scala('scala/concurrent/duration/FiniteDuration'),
ExecutionContext: scala('scala/concurrent/ExecutionContext'),
'scala.util.Using.resource': scala('scala/util/Using$'),
Using: scalaUsing, 'scala.util.Using': scalaUsing,
'Using.resource': `${scalaUsing}#resource-fffffb81`,
'scala.util.Using.resource': `${scalaUsing}#resource-fffffb81`,
IO: 'https://typelevel.org/cats-effect/api/3.x/cats/effect/IO.html',
Resource: 'https://typelevel.org/cats-effect/api/3.x/cats/effect/kernel/Resource.html',
},
Expand Down
75 changes: 75 additions & 0 deletions packages/api-model/src/doc/go.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { Parser } from 'web-tree-sitter'
import type { ParsedMarkdownDoc } from './markdown.ts'

const LIST = /^[ \t]*(?:[-+*•]|\d+[.)])[ \t]+\S/
const FENCE = /^ {0,3}(`{3,}|~{3,})([^`~]*)$/

/** Native Go documentation removes a common literal space/tab prefix. */
function unindent(source: string[]): string[] {
const lines = [...source]
while (lines.length && !lines[0].trim()) lines.shift()
while (lines.length && !lines.at(-1)!.trim()) lines.pop()
let prefix = /^[ \t]*/.exec(lines[0] ?? '')![0]
for (const line of lines.filter((line) => line.trim())) {
while (!line.startsWith(prefix)) prefix = prefix.slice(0, -1)
}
return lines.map((line) => line.trim() ? line.slice(prefix.length) : '')
}

/** Syntax classification preserves non-Go preformatted output as plain text. */
function codeLanguage(code: string, parser: Parser): 'go' | 'text' {
for (const source of [`package doc\n${code}`, `package doc\nfunc example() {\n${code}\n}`]) {
const tree = parser.parse(source)
const valid = tree && !tree.rootNode.hasError
tree?.delete()
if (valid) return 'go'
}
return 'text'
}

/** Preserve native block boundaries in Markdown without moving code past prose. */
export function parseGoDoc(raw: string, parser: Parser): ParsedMarkdownDoc {
const lines = unindent(raw.replaceAll('\r\n', '\n').split('\n'))
const blocks: { kind: 'prose' | 'other'; text: string }[] = []
for (let index = 0; index < lines.length;) {
const line = lines[index]
if (!line.trim()) { index++; continue }
const fence = FENCE.exec(line)
if (fence) {
const code = [fence[2].trim() ? line : `${fence[1]}go`]
const end = new RegExp(`^ {0,3}${fence[1][0]}{${fence[1].length},}\\s*$`)
while (++index < lines.length) {
code.push(lines[index])
if (end.test(lines[index])) { index++; break }
}
blocks.push({ kind: 'other', text: code.join('\n') })
continue
}
if (/^[ \t]/.test(line)) {
const indented: string[] = []
while (index < lines.length && (!lines[index].trim() || /^[ \t]/.test(lines[index]))) {
indented.push(lines[index++])
}
const content = unindent(indented).join('\n')
if (LIST.test(line)) {
blocks.push({ kind: 'other', text: content.replace(/^([ \t]*)•(?=[ \t])/gm, '$1-')
.replace(/^([ \t]*\d+)\)(?=[ \t])/gm, '$1.') })
} else {
const marker = '`'.repeat(Math.max(3, ...[...content.matchAll(/`+/g)].map((match) => match[0].length + 1)))
blocks.push({ kind: 'other', text: `${marker}${codeLanguage(content, parser)}\n${content}\n${marker}` })
}
continue
}
if (/^#\s+/.test(line)) {
blocks.push({ kind: 'other', text: line }); index++; continue
}
const prose: string[] = []
while (index < lines.length && lines[index].trim() && !/^[ \t]|^#\s+/.test(lines[index]) && !FENCE.test(lines[index])) {
prose.push(lines[index++])
}
blocks.push({ kind: LIST.test(line) ? 'other' : 'prose', text: prose.join('\n') })
}
const first = blocks[0]?.kind === 'prose' ? blocks.shift()!.text : ''
return { doc: { summary: first.replace(/\s+/g, ' ').trim(), body: blocks.map((block) => block.text).join('\n\n') || undefined },
params: new Map(), raises: [] }
}
146 changes: 121 additions & 25 deletions packages/api-model/src/doc/roles.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { fromMarkdown } from 'mdast-util-from-markdown'

/**
* reST cross-reference roles, which are most of the interlinking.
*
Expand Down Expand Up @@ -57,6 +59,13 @@ export type DocSpan =
* "search upward", and the label drops it.
*/
const ROLE_RE = /:([a-z:]+):`([^`]+)`/g
/** Embedded reST hyperlinks, including anonymous links with two underscores. */
const REST_LINK_RE = /`([^`<>]+?)(\s*)<((?:\\[\s\S]|[^\\<>`])+)>`_{1,2}/g
/** reST distinguishes opening and closing ASCII delimiters from Unicode punctuation. */
/* eslint-disable no-control-regex -- Docutils also treats U+001C through U+001F as whitespace. */
const REST_LINK_START_RE = /(?:[\p{White_Space}\u001c-\u001f"'(<[{/:-]|(?=[^\x00-\x7f])[\p{Ps}\p{Pi}\p{Pf}\p{Pd}\p{Po}])$/u
const REST_LINK_END_RE = /^(?:[\p{White_Space}\u001c-\u001f"')>\]}/:.,;!?\\-]|(?=[^\x00-\x7f])[\p{Pe}\p{Pi}\p{Pf}\p{Pd}\p{Po}])/u
/* eslint-enable no-control-regex */

/**
* A citation reference: `[server_manual]_`.
Expand Down Expand Up @@ -169,9 +178,35 @@ const EMPHASIS_RE = /(?<![\w*\\])\*(?![\s*])([^*\n]+?)(?<![\s\\])\*(?![\w*])/g

/** A bare double-backtick literal, which is never a link. */
const LITERAL_RE = /``([^`]+)``/g
/** reST literals can contain single backticks without enabling nested markup. */
const REST_LITERAL_RE = /``((?:[^`]|`(?!`))+)``/g
/** A single-backtick reference, which napoleon also resolves. */
const BACKTICK_RE = /`([^`<>]+)`/g

/** Match equal-length Markdown delimiters without interpreting their code as reST. */
function markdownCode(text: string, masked: string): { start: number; end: number; text: string; ticks: number }[] {
const runs = [...masked.matchAll(/`+/g)]
const next = new Map<number, number>()
const closing = runs.map(() => -1)
for (let i = runs.length - 1; i >= 0; i--) {
closing[i] = next.get(runs[i][0].length) ?? -1
next.set(runs[i][0].length, i)
}
const spans: ReturnType<typeof markdownCode> = []
for (let i = 0; i < runs.length; i++) {
const open = runs[i]
let slashes = 0
for (let at = open.index - 1; at >= 0 && text[at] === '\\'; at--) slashes++
if (slashes % 2 || closing[i] < 0) continue
const close = runs[closing[i]]
let code = text.slice(open.index + open[0].length, close.index).replace(/\r\n?|\n/g, ' ')
if (code.startsWith(' ') && code.endsWith(' ') && /[^ ]/.test(code)) code = code.slice(1, -1)
spans.push({ start: open.index, end: close.index + close[0].length, text: code, ticks: open[0].length })
i = closing[i]
}
return spans
}

function labelFor(raw: string): { target: string; label: string } {
const titled = /^(.*?)\s*<([^>]+)>$/.exec(raw.trim())
if (titled) return { target: titled[2].trim(), label: titled[1].trim() }
Expand Down Expand Up @@ -213,7 +248,7 @@ const SYNTAX: Record<string, ReadonlySet<string>> = {
rs: new Set(['intra-doc']),
go: new Set(['go-link']),
java: new Set(['jsdoc']),
ts: new Set(['jsdoc']),
ts: new Set(['jsdoc', 'markdown-link']),
csharp: new Set(['xml-doc']),
// DocC gives double backticks a meaning reST reserves for a literal: in a
// Swift doc comment ``Server`` is a symbol link, not code.
Expand Down Expand Up @@ -249,20 +284,38 @@ export function tokenizeDoc(text: string, lang?: string): DocSpan[] {
type Hit = { start: number; end: number; span: DocSpan }
const hits: Hit[] = []

if (syntax.has('markdown-link')) {
// A code label can contain brackets (generic types, regexes, array values)
// and use a longer backtick fence when the code itself contains backticks.
for (const match of text.matchAll(/\[(?:(`+)([^\n]*?)\1|([^\]\n]+))\]\(([^\n)]+)\)/g)) {
const label = match[1] ? match[2] : match[3]
const target = match[4].trim()
if (syntax.has('documenter') && /^@ref(?:\s|$)/.test(target)) {
hits.push({ start: match.index, end: match.index + match[0].length,
span: { kind: 'ref', role: 'any', target: target.slice(4).trim() || label, label } })
} else if (/^(?:https?:\/\/|\/(?!\/)|#)/.test(target)) {
hits.push({ start: match.index, end: match.index + match[0].length,
span: { kind: 'link', text: label, href: target, ...(match[1] ? { code: true } : {}) } })
if (syntax.has('documenter')) {
// Documenter's named @ref destination contains spaces that Markdown URLs cannot.
for (const match of text.matchAll(/\[(?:(`+)([^\n]*?)\1|([^\]\n]+))\]\(@ref\s+([^\n)]+)\)/g)) {
hits.push({ start: match.index, end: match.index + match[0].length,
span: { kind: 'ref', role: 'any', target: match[4].trim(), label: match[1] ? match[2] : match[3] } })
}
}
if (syntax.has('markdown-link') && text.includes('](')) {
// Authored links own their labels, including generic code and balanced URI parentheses.
type MarkdownNode = ReturnType<typeof fromMarkdown> | ReturnType<typeof fromMarkdown>['children'][number]
const labelText = (node: MarkdownNode): string => 'value' in node ? node.value
: 'children' in node ? node.children.map((child) => labelText(child as MarkdownNode)).join('')
: 'alt' in node ? node.alt ?? '' : ''
const visit = (node: MarkdownNode) => {
if (node.type === 'link') {
const start = node.position!.start.offset!
const end = node.position!.end.offset!
const label = labelText(node)
const target = node.url
let span: DocSpan = { kind: 'text', text: text.slice(start, end) }
if (syntax.has('documenter') && /^@ref(?:\s|$)/.test(target)) {
span = { kind: 'ref', role: 'any', target: target.slice(4).trim() || label, label }
} else if (/^https?:\/\//i.test(target) || !/^(?:[a-z][\w+.-]*:|\/\/)/i.test(target)) {
span = { kind: 'link', text: label, href: target,
...(node.children.length === 1 && node.children[0].type === 'inlineCode' ? { code: true } : {}) }
}
hits.push({ start, end, span })
return
}
if ('children' in node) for (const child of node.children) visit(child as Parameters<typeof visit>[0])
}
visit(fromMarkdown(text))
}

for (const match of text.matchAll(/<kbd>([^<\n]+)<\/kbd>/g)) {
Expand Down Expand Up @@ -392,35 +445,78 @@ export function tokenizeDoc(text: string, lang?: string): DocSpan[] {
span: { kind: 'ref', role, target, label },
})
}
for (const m of text.matchAll(LITERAL_RE)) {
if (syntax.has('rest')) for (const m of text.matchAll(REST_LITERAL_RE)) {
hits.push({
start: m.index,
end: m.index + m[0].length,
// DocC's ``Symbol`` is a link where reST's ``literal`` is code.
span: syntax.has('docc')
? { kind: 'ref', role: 'any', target: m[1].replace(/\(.*$/, ''), label: m[1] }
: { kind: 'code', text: m[1] },
span: { kind: 'code', text: m[1] },
})
}
if (syntax.has('rest')) {
for (const match of text.matchAll(REST_LINK_RE)) {
const start = match.index
const end = start + match[0].length
if (hits.some((hit) => hit.start < end && hit.end > start)) continue
const rawLabel = match[1]
const separator = match[2]
const rawTarget = match[3]
let span: DocSpan = { kind: 'text', text: match[0] }
const delimited = (start === 0 || REST_LINK_START_RE.test(text.slice(Math.max(0, start - 2), start)))
&& (end === text.length || REST_LINK_END_RE.test(text.slice(end, end + 2)))
/* eslint-disable no-control-regex -- Match Docutils whitespace and its NUL escape markers. */
const validWhitespace = !/^[\p{White_Space}\u001c-\u001f]/u.test(rawLabel)
&& /^[ \t\r\n]+$/.test(separator)
&& !/^[\p{White_Space}\u001c-\u001f]|[\p{White_Space}\u001c-\u001f]$/u.test(rawTarget)
if (delimited && validWhitespace) {
const label = rawLabel.replace(/\\([ \n])|\\([\s\S]?)/g, (_match, _space, char: string | undefined) => char ?? '')
// Docutils folds URI whitespace before removing native escape markers.
const target = rawTarget.replace(/\\([\s\S]?)/g, '\u0000$1').split(/\u0000[ \n]/)
.map((part) => part.replace(/[\p{White_Space}\u001c-\u001f]/gu, '')).join(' ')
.replaceAll('\u0000', '').replace(/\\_$/, '_')
try {
const destination = new URL(target)
if (/^https?:\/\//i.test(target) && (destination.protocol === 'http:' || destination.protocol === 'https:')) {
span = { kind: 'link', text: label, href: target }
}
} catch {
// Malformed destinations remain literal.
}
}
/* eslint-enable no-control-regex */
hits.push({ start, end, span })
}
}
// Single-backtick references are scanned over a *masked* copy, with every
// range already claimed by a role or a literal blanked out. Filtering
// matches afterwards is not equivalent and quietly loses references: the
// pairing is greedy and left-to-right, so an unclaimed backtick can pair
// with one inside a role and swallow everything between them. In
// ``:meth:`send keys <Pane.send_keys>` plus `Window`.`` that consumed the
// `Window` reference entirely.
// Native tags can contain unmatched ticks; they cannot consume a later code opener.
// Code units, not code points: `hits` are regex indices into the string.
const masked = text.split('')
for (const hit of hits) {
for (let i = hit.start; i < hit.end; i++) masked[i] = ' '
}
for (const m of masked.join('').matchAll(BACKTICK_RE)) {
const { target, label } = labelFor(m[1])
hits.push({
start: m.index,
end: m.index + m[0].length,
span: { kind: 'ref', role: 'any', target, label },
})
if (syntax.has('rest')) {
for (const m of masked.join('').matchAll(BACKTICK_RE)) {
const { target, label } = labelFor(m[1])
hits.push({
start: m.index,
end: m.index + m[0].length,
span: { kind: 'ref', role: 'any', target, label },
})
}
} else {
for (const code of markdownCode(text, masked.join(''))) {
const span: DocSpan = code.ticks === 1
? { kind: 'ref', role: 'any', target: code.text, label: code.text }
: syntax.has('docc') && code.ticks === 2
? { kind: 'ref', role: 'any', target: code.text.replace(/\(.*$/, ''), label: code.text }
: { kind: 'code', text: code.text }
hits.push({ start: code.start, end: code.end, span })
}
}

for (const match of text.matchAll(EMPHASIS_RE)) {
Expand Down
52 changes: 52 additions & 0 deletions packages/api-model/src/javadoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
interface JavadocPackage {
l: string
m?: string
}

interface JavadocType {
l: string
p?: string
m?: string
u?: string
}

interface JavadocMember extends JavadocType {
c?: string
}

/** Convert Javadoc search indexes without guessing a package's module. */
export function javadocSymbols(
packages: JavadocPackage[],
types: JavadocType[],
members: JavadocMember[],
) {
const owners = new Map<string, Set<string>>()
for (const pkg of packages) {
if (!pkg.m) continue
const modules = owners.get(pkg.l) ?? new Set<string>()
modules.add(pkg.m)
owners.set(pkg.l, modules)
}
const moduleOf = (entry: JavadocType) => {
const modules = owners.get(entry.p!)
if (entry.m && modules?.has(entry.m)) return entry.m
if (!entry.m && modules?.size === 1) return [...modules][0]
throw new Error(`Javadoc package ${entry.p}: ${entry.m ? `unknown module ${entry.m}` : 'missing or ambiguous module'}`)
}
const symbols: { id: string; name: string; kind: 'class' | 'method'; uri: string }[] = []
for (const type of types) {
if (!type.p || !type.l) continue
const path = `${moduleOf(type)}/${type.p.replace(/\./g, '/')}/${type.u ?? `${type.l}.html`}`
symbols.push({ id: `${type.p}.${type.l}`, name: type.l, kind: 'class', uri: path })
symbols.push({ id: type.l, name: type.l, kind: 'class', uri: path })
}
for (const member of members) {
if (!member.p || !member.c || !member.l) continue
const bare = member.l.replace(/\(.*$/, '')
if (!bare || /[^A-Za-z0-9_$]/.test(bare)) continue
const path = `${moduleOf(member)}/${member.p.replace(/\./g, '/')}/${member.c}.html#${member.u ?? member.l}`
symbols.push({ id: `${member.c}.${bare}`, name: bare, kind: 'method', uri: path })
symbols.push({ id: `${member.p}.${member.c}.${bare}`, name: bare, kind: 'method', uri: path })
}
return symbols
}
Loading
Loading