Problem
Every language feature handler (completion, hover, definition, rename, references, symbols, folding, formatting) and the diagnostics handler each independently call parseXMLDocument() on the same document text. There is no shared parse result cache.
This means a single user interaction can trigger multiple redundant parses of the same unchanged document:
user types a character
→ didChange → DiagnosticsHandler → parseXMLDocument() (parse #1)
→ user hovers → requestHandlers → parseXMLDocument() (parse #2)
→ user triggers completion → parseXMLDocument() (parse #3)
Affected locations
diagnosticsHandler.ts line 53
const xmlDoc = this.service.parseXMLDocument(document.uri, text);
requestHandlers.ts — called independently in every handler:
// completion (line 41)
const xmlDoc = service.parseXMLDocument(document.uri, document.getText());
// hover (line 64)
const xmlDoc = service.parseXMLDocument(document.uri, document.getText());
// documentSymbol (line 74)
const xmlDoc = service.parseXMLDocument(document.uri, document.getText());
// foldingRanges, rename, definition, references, formatting — same pattern
Impact
- Redundant CPU work on every keystroke
- Adds latency to hover, completion, and other features especially for large XML files
- Gets worse as more feature handlers are added
Proposed Solution
Introduce a lightweight parse cache keyed by document URI and document version. The TextDocument object from vscode-languageserver-textdocument exposes a version number that increments on every content change — this makes it a natural cache invalidation key.
interface ParseCacheEntry {
version: number;
xmlDoc: XMLDocument;
}
const parseCache = new Map<string, ParseCacheEntry>();
function getParsedDoc(document: TextDocument): XMLDocument {
const cached = parseCache.get(document.uri);
if (cached?.version === document.version) return cached.xmlDoc; // reuse ✅
const xmlDoc = service.parseXMLDocument(document.uri, document.getText());
parseCache.set(document.uri, { version: document.version, xmlDoc });
return xmlDoc;
}
- Same document, same version → return cached AST instantly, no re-parse
- Document edited → version increments → re-parse once, update cache
- Document closed → cache entry should be removed to avoid memory leaks
Where the cache should live
The cache should be shared between diagnosticsHandler.ts and requestHandlers.ts so that a parse triggered by validation is reused by the next hover or completion request on the same document version. It can be passed as a dependency during registerRequestHandlers() and DiagnosticsHandler construction, or extracted into a small XmlDocumentCache utility class.
Cache eviction on document close
Wire up documents.onDidClose in server.ts to remove the entry:
documents.onDidClose((e) => {
parseCache.delete(e.document.uri);
});
Acceptance Criteria
Problem
Every language feature handler (completion, hover, definition, rename, references, symbols, folding, formatting) and the diagnostics handler each independently call
parseXMLDocument()on the same document text. There is no shared parse result cache.This means a single user interaction can trigger multiple redundant parses of the same unchanged document:
Affected locations
diagnosticsHandler.tsline 53requestHandlers.ts— called independently in every handler:Impact
Proposed Solution
Introduce a lightweight parse cache keyed by document URI and document version. The
TextDocumentobject fromvscode-languageserver-textdocumentexposes aversionnumber that increments on every content change — this makes it a natural cache invalidation key.Where the cache should live
The cache should be shared between
diagnosticsHandler.tsandrequestHandlers.tsso that a parse triggered by validation is reused by the next hover or completion request on the same document version. It can be passed as a dependency duringregisterRequestHandlers()andDiagnosticsHandlerconstruction, or extracted into a smallXmlDocumentCacheutility class.Cache eviction on document close
Wire up
documents.onDidCloseinserver.tsto remove the entry:Acceptance Criteria
parseXMLDocument()is called at most once per document version across all handlersversionchanges)DiagnosticsHandlerandrequestHandlers.tsuse the shared cache