Document links
textDocument/documentLink for http(s) URLs in comments and strings, and documentLink/resolve tooltip.
This page
Abstract
textDocument/documentLink reports http:// / https:// URLs found in comments (//, /* */, hash # …) and string literals. Each match is a DocumentLink whose range is the URL (not the surrounding quotes or comment markers) and whose target is that URL. Empty source or parse failure returns []. The first response leaves tooltip unset; documentLink/resolve copies the URL onto tooltip when it is unset so clients can show it on hover. Advertised as DocumentLinkOptions(resolve_provider=True).
Conceptual model
Rendering…
Comments are scanned from the raw buffer so they do not need AST nodes. String URLs come from Constant nodes whose value is a str. Hash comments are distinguished from #rgb color tokens: # followed by a hex digit is a color, not a comment.
Interface surface
| Method | Capability |
|---|---|
textDocument/documentLink | DocumentLinkOptions(resolve_provider=True) |
documentLink/resolve | fills tooltip with the URL when tooltip is unset |
Handlers: features/document_link.py → handle_document_link (receives version-matched tree=doc.valid_ast) and handle_document_link_resolve.
Each initial result:
| Field | Value |
|---|---|
range | LSP range covering the URL text |
target | The URL string (http:// or https://) |
tooltip | unset (None) until resolve |
Returns [] for empty source, parse failure, or tree=None.
Where URLs are collected
| Region | Source |
|---|---|
// line comments | Raw buffer, through the newline (or EOF) |
/* */ block comments | Raw buffer, including the closers; unclosed blocks run to EOF |
Hash # … comments | Raw buffer, when # is not a #rgb color token |
| String literals | AST Constant with a str value; range is the URL inside the quotes |
The matcher is http:// or https:// followed by non-whitespace characters excluding <>"' and *. Trailing .,;:!? is stripped from both the range and the target (// see https://example.com/end. → https://example.com/end).
Not collected:
ftp://…, scheme-lesswww.example.com, or other non-http(s)schemes- URLs in identifier / number tokens (only comments and string Constants)
#hexcolor literals (they are not hash comments)
Resolve
documentLink/resolve fills tooltip with the link target URL:
- An existing tooltip is kept (already-resolved links are returned unchanged).
- When tooltip is unset and
targetis a non-empty string,tooltipis set to that URL. - Empty or malformed links are returned unchanged (never raises).
Initial textDocument/documentLink responses already carry target. Resolve copies that URL onto tooltip so clients can show it on hover.
Internals
| Path | Role |
|---|---|
src/pynescript/langserver/features/document_link.py | Comment scan + string walk + resolve |
src/pynescript/langserver/config.py | DocumentLinkOptions(resolve_provider=True) |
src/pynescript/langserver/server.py | TEXT_DOCUMENT_DOCUMENT_LINK (cached on doc.feature_cache["document_link"]); DOCUMENT_LINK_RESOLVE |
The workspace cache supplies tree=doc.valid_ast. Pass tree=None when the last parse failed (skips a redundant re-parse). Omit tree only in isolated tests (handler then parses from source). Comments need the source text; a pre-parsed tree with source=None still yields string-literal links.
Tests: tests/test_lsp_features.py (TestDocumentLinkHandler).
Invariants and edge cases
- Empty / unparsed →
[]. Missing source, parse exception, ortree=Nonenever returnsNone. - Range is the URL, not the quotes.
"https://example.com"highlightshttps://example.com. - Trailing punctuation is not part of the link.
https://example.com/end.strips the period. - Hash comments vs colors.
# hash https://…is a comment;#ff0000is a color token and is not scanned as a comment. - Results are sorted by start line, then start column.
- Workspace cache. The server stores the
textDocument/documentLinkresult onfeature_cacheuntil the buffer changes. Resolve is not cached; it fillstooltipon the link the client sends back. - Resolve fills tooltip only when unset. An existing tooltip is kept; empty or malformed links are returned as-is and never throw.
Worked example
//@version=6
indicator("T")
// docs: https://example.com/pine
url = "https://tradingview.com/pine-script-docs"
also = 'http://example.org/path'
/* block https://example.com/block and https://example.com/two */
# hash https://example.com/hash
// trailing https://example.com/end.
plot(close)
Expected links (target equals the range text; tooltip is unset until resolve):
https://example.com/pinehttps://tradingview.com/pine-script-docshttp://example.org/pathhttps://example.com/blockhttps://example.com/twohttps://example.com/hashhttps://example.com/end(period not included)
After documentLink/resolve, each of those links has tooltip equal to target (for example https://example.com/pine). A client that already set a tooltip keeps that string.
ftp://example.com and www.example.com in a comment produce no links.
Failure modes
| Symptom | Cause |
|---|---|
| No clickable URLs | Parse failed ([]), or the URL is not http(s) |
| Link includes a trailing period | Client ignored the range and used line text; the server strips .,;:!? |
| Comment URL missing, string URL present | Handler ran with source=None and a cached tree — comments need the buffer |
#ff0000 not a link | Expected: hash color tokens are not comments |
| Hover over a link shows no tooltip | Client skipped documentLink/resolve, or the link had no target |
| Tooltip overwritten | Not expected: resolve keeps an existing tooltip |
See also
- Document color —
#hexvs hash comments - Architecture — declared capabilities and AST cache
- Hover — Pine v6 function-reference links on builtin cards