This is an automated email from the ASF dual-hosted git repository.
jeremyyao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil-vscode.git
The following commit(s) were added to refs/heads/main by this push:
new b83cb92 Document elementCompletion.ts and elementItems.ts in relation
to documenting ElementCompletionProvider Intellisense functionaltiy
b83cb92 is described below
commit b83cb92afafe8209c4f0107b5a4c3ffe50e66969
Author: Jeremy Yao <[email protected]>
AuthorDate: Mon Jan 26 21:13:24 2026 -0500
Document elementCompletion.ts and elementItems.ts in relation to
documenting ElementCompletionProvider Intellisense functionaltiy
Closes #1488
---
src/language/intellisense-development.md | 140 +++++++++++++-
src/language/providers/elementCompletion.ts | 208 +++++++++++++++++----
.../providers/intellisense/elementItems.ts | 116 +++++++-----
3 files changed, 373 insertions(+), 91 deletions(-)
diff --git a/src/language/intellisense-development.md
b/src/language/intellisense-development.md
index a4128c7..c148878 100644
--- a/src/language/intellisense-development.md
+++ b/src/language/intellisense-development.md
@@ -43,10 +43,12 @@ This document contains an overview of how Intellisense
works, as well as a gener
- [attributeValueCompletion.ts](#attributevaluecompletionts)
- [closeElement.ts](#closeelementts)
- [closeElementSlash.ts](#closeelementslashts)
+ - [elementCompletion.ts](#elementcompletionts)
- [closeUtils.ts](#closeutilsts)
- [Intellisense Data Files (intellisense
subdirectory)](#intellisense-data-files-intellisense-subdirectory)
- [attributeItems.ts](#attributeitemsts)
- [attributeValueItems.ts](#attributevalueitemsts)
+ - [elementItems.ts](#elementitemsts)
- [src/language/dfdl.ts](#srclanguagedfdlts)
- [TDML](#tdml)
@@ -314,6 +316,77 @@ Hover tooltips can be found under `attributeHoverValues()`
in `attributeHoverIte
5. `checkItemsOnLine()` decides: self-closing tag, full closing tag, or nothing
6. Inserts appropriate snippet at the correct position
+###### elementCompletion.ts
+
+**Purpose:** Element name completion provider for XML elements in DFDL schemas
and TDML test files. Suggests valid child elements when the user presses
newline inside an XML element.
+
+**Key Functionality:**
+
+- Provides context-aware element suggestions based on DFDL schema hierarchy
and TDML structure
+- Respects DFDL schema relationships (e.g., inside `sequence` suggests
`element`, `choice`, etc.)
+- Special handling for annotation/appinfo contexts to provide DFDL-specific
elements based on parent type
+- Scans document for user-defined variables to populate `dfdl:setVariable`
completion choices
+- Prevents suggestions when cursor is in inappropriate contexts (XPath,
quotes, braces, after equals, inside open elements with no missing close tags)
+- Handles multi-item lines with complex cursor positioning logic
+- Dynamic namespace prefix handling (xs:, xsd:, dfdl:)
+- Separate provider implementations for DFDL (full validation) and TDML
(simpler logic)
+
+**Key Functions:**
+
+- `getElementCompletionProvider()`: Main DFDL document provider registration
+- `getTDMLElementCompletionProvider()`: TDML document provider registration
+- `getElementCompletionItems()`: Creates completion items from elementItems.ts
data based on context
+- `getDefinedVariables()`: Scans document for `dfdl:defineVariable`
declarations to extract variable names
+- `nearestOpenTagChildElements()`: Core logic determining which child elements
are valid for a given parent (main DFDL hierarchy switch statement)
+- `getAnnotationParent()`: Finds the parent element of an annotation to
provide correct DFDL format elements in appinfo context
+- `getTagNearestTrigger()`: Complex algorithm to find the nearest tag to
cursor position, handling single-line and multi-line scenarios
+
+**Trigger:** Newline (`\n`)
+
+**Architecture Notes:**
+
+- Uses **guard clauses** for early returns in invalid contexts
+- Implements **dual strategy**: robust DFDL provider vs. simpler TDML provider
+- **Complex multi-tag line handling**: Extensive logic for scenarios with
multiple tags per line
+- **Appinfo context chaining**: Traverses annotation → appinfo → parent
element to determine valid DFDL elements
+- **Dynamic completion construction**: Variable names dynamically added to
`dfdl:setVariable` suggestions
+- **Provider pattern**: Registers as standard VS Code completion provider
+- **Progressive tab-stop navigation**: Uses `$1`, `$2`, `$0` for guided
element construction
+
+**Dependencies:**
+
+- `closeUtils`: `checkMissingCloseTag()` for determining tag closure state
+- `utils`: Extensive use including `checkBraceOpen()`, `cursorWithinBraces()`,
`cursorWithinQuotes()`, `cursorAfterEquals()`, `isInXPath()`,
`isTagEndTrigger()`, `nearestOpen()`, `nearestTag()`, `getAttributeNames()`,
`getItemsOnLineCount()`, `getNsPrefix()`, `createCompletionItem()`
+- `intellisense/elementItems`: `elementCompletion()` factory function for
completion data
+- `XmlItem` class from utils for encapsulating parsed element data
+
+**DFDL Hierarchy Rules (from `nearestOpenTagChildElements()`):**
+
+The function implements the complete DFDL Schema hierarchy through a large
switch statement:
+
+- `element` → `complexType`, `simpleType`, `annotation`
+- `sequence` → `element`, `sequence`, `choice`, `annotation`
+- `choice` → `element`, `sequence`, `group`, `annotation`
+- `group` → `sequence`, `annotation`
+- `complexType` → `sequence`, `group`, `choice`, `annotation`
+- `simpleType` → `annotation`, `restriction`
+- `annotation` → `appinfo`
+- `appinfo` → DFDL elements based on annotation parent (`dfdl:format`,
`dfdl:element`, `dfdl:sequence`, etc.)
+- `assert/discriminator` → `CDATA`, `{}`
+- `schema` root → `sequence`, `element`, `choice`, `group`, `complexType`,
`simpleType`, `annotation`, `include`, `import`, `defineVariable`
+- `emptySchema` → `xml version` declaration
+
+**Flow:**
+
+1. User presses newline inside an XML element
+2. Guard clauses validate context (not XPath, quotes, braces, after equals,
inside properly closed element)
+3. Extract namespace prefix and scan for user-defined variables
+4. `nearestOpen()` finds the parent element at cursor position
+5. `getTagNearestTrigger()` determines which tag should trigger completion
(handles multi-item lines)
+6. `nearestOpenTagChildElements()` uses switch statement to determine valid
child elements for parent
+7. `getElementCompletionItems()` filters elementItems.ts data for valid
elements only
+8. Returns completion items with proper namespace prefixes and documentation
+
###### closeUtils.ts
**Purpose:** Core utility module providing tag state analysis functions for
the auto-completion system. Determines whether XML/DFDL tags are properly
closed and identifies unclosed tags that need completion.
@@ -406,8 +479,8 @@ Hover tooltips can be found under `attributeHoverValues()`
in `attributeHoverIte
**Attribute Categories:**
- XSD Core Attributes (name, ref, type, minOccurs, maxOccurs)
-- DFDL Length Properties (dfdl: length, dfdl:lengthKind, dfdl:lengthUnits)
-- DFDL Encoding Properties (dfdl: encoding, dfdl:encodingErrorPolicy)
+- DFDL Length Properties (dfdl:length, dfdl:lengthKind, dfdl:lengthUnits)
+- DFDL Encoding Properties (dfdl:encoding, dfdl:encodingErrorPolicy)
- DFDL Text/Binary Representation
- DFDL Separators/Delimiters
- DFDL Calendar/Date
@@ -449,16 +522,71 @@ Hover tooltips can be found under
`attributeHoverValues()` in `attributeHoverIte
- `noChoiceAttributes`: List of attributes without predefined choices
- `attributeValues(attributeName, startPos, additionalTypes)`: Inserts
appropriate snippet at cursor
+###### elementItems.ts
+
+**Purpose:** Static completion data for XML elements used in DFDL/XSD
documents. Provides a complete catalog of XML/DFDL elements that can be
suggested in different schema contexts, along with their snippet templates and
documentation.
+
+**Data Structure:**
+
+- Factory function `elementCompletion(definedVariables, nsPrefix)` that
returns an object with an `items` array
+- Each item contains:
+ - `item`: Element name (can include namespace prefix)
+ - `snippetString`: VS Code snippet with tab stops, placeholders, and default
values
+ - `markdownString`: Documentation shown in completion tooltip
+
+**Snippet Features:**
+
+- **Tab stops**: `$1`, `$2`, `$0` for progressive navigation
+- **Choice placeholders**: `${1|option1,option2|}` for dropdown selections
+- **Dynamic variable insertion**: Uses `definedVariables` parameter to
populate `dfdl:setVariable` ref attribute choices
+- **Namespace prefix handling**: Accepts `nsPrefix` parameter to customize
snippets with correct namespace (xs:, xsd:, etc.)
+- **Multi-line templates**: Many elements include proper indentation and
structure
+
+**Element Categories:**
+
+- **XML Declaration**: `xml version` - XML prolog with UTF-8 encoding
+- **XSD Core Elements**: `schema`, `element` (name/ref variants),
`complexType`, `simpleType`, `sequence`, `choice`, `group`, `annotation`,
`appinfo`, `restriction`, `include`, `import`
+- **DFDL Property Elements**: `dfdl:format`, `dfdl:property`, `dfdl:assert`,
`dfdl:discriminator`, `dfdl:escapeScheme`
+- **DFDL Variable Elements**: `dfdl:defineVariable`, `dfdl:setVariable`,
`dfdl:newVariableInstance`
+- **DFDL Format Definition Elements**: `dfdl:defineFormat`,
`dfdl:defineEscapeScheme`
+- **DFDL Container Elements**: `dfdl:element`, `dfdl:sequence`, `dfdl:choice`,
`dfdl:group`
+- **XSD Restriction Elements**: `minInclusive`, `minExclusive`,
`maxInclusive`, `maxExclusive`, `pattern`, `totalDigits`, `fractionDigits`,
`enumeration`
+- **Special Constructs**: `CDATA`, `{}` DFDL expression wrapper
+
+**Key Export:**
+
+- `elementCompletion(definedVariables, nsPrefix)`: **Factory function** that
generates completion data dynamically based on:
+ - `definedVariables`: Comma-separated string of variable names for
`dfdl:setVariable` dropdown
+ - `nsPrefix`: Namespace prefix to use for XSD elements (typically "xs:" or
"xsd:")
+
+**Dynamic Behavior:**
+
+Unlike static attribute files, `elementItems.ts` is a **factory function**
that constructs completion data at runtime. This enables:
+
+- Dynamic population of variable names from the document
+- Customizable namespace prefixes based on document context
+- Flexible snippet construction that adapts to schema conventions
+
+**Example Usage:**
+
+```typescript
+// In elementCompletion.ts
+const definedVariables = getDefinedVariables(document) // "var1,var2,var3"
+const nsPrefix = getNsPrefix(document, position) // "xs:"
+const elementData = elementCompletion(definedVariables, nsPrefix)
+// elementData.items now contains customized snippets with proper vars and
prefixes
+```
+
##### src/language/dfdl.ts
**Purpose:** The central registration file for all DFDL language features.
**Key Functionality:**
-- **Starting Point:** It is executed during extension activation and registers
the completion and hover providers with VS Code. This file is the “entry point”
for Language features. If you want to add providers, this is the place to
update.
-- **Central Rgistration Point:**It imports provider modules (element,
attribute, attributeValue, closeElement, attributeHover, etc.) and registers
them with appropriate trigger characters (e.g., `<`, `:`, `"`, `=`, `/`,
`whitespace`).
-- **Wiring:**It does not perform completion logic itself — rather it wires VS
Code events to the exported provider objects/functions in the provider modules.
-- **DFDL Language Provider:**When VS Code triggers a provider callback for the
DFDL language, the registered provider function from the corresponding module
is invoked.
+- **Starting Point:** It is executed during extension activation and registers
the completion and hover providers with VS Code. This file is the "entry point"
for Language features. If you want to add providers, this is the place to
update.
+- **Central Registration Point:** It imports provider modules (element,
attribute, attributeValue, closeElement, attributeHover, etc.) and registers
them with appropriate trigger characters (e.g., `<`, `:`, `"`, `=`, `/`,
`whitespace`).
+- **Wiring:** It does not perform completion logic itself — rather it wires VS
Code events to the exported provider objects/functions in the provider modules.
+- **DFDL Language Provider:** When VS Code triggers a provider callback for
the DFDL language, the registered provider function from the corresponding
module is invoked.
##### src\language\providers\utils.ts
diff --git a/src/language/providers/elementCompletion.ts
b/src/language/providers/elementCompletion.ts
index fea2132..f62056a 100644
--- a/src/language/providers/elementCompletion.ts
+++ b/src/language/providers/elementCompletion.ts
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
import * as vscode from 'vscode'
import { checkMissingCloseTag, getCloseTag } from './closeUtils'
import {
@@ -33,11 +32,41 @@ import {
cursorAfterEquals,
} from './utils'
import { elementCompletion } from './intellisense/elementItems'
-
+/**
+ * Registers an element completion provider for DFDL language files.
+ * This provider suggests child elements when the user is inside an XML element
+ * and types a newline, helping to construct DFDL schema structures.
+ *
+ * **Trigger:** Newline character (`\n`)
+ *
+ * **Key Behaviors:**
+ * - Provides context-aware element suggestions based on the parent element
+ * - Respects DFDL schema hierarchy (e.g., inside `sequence` suggests
`element`, `choice`, etc.)
+ * - Handles annotation/appinfo contexts specially for DFDL format definitions
+ * - Scans document for defined variables to include in completions
+ * - Prevents suggestions when cursor is in inappropriate contexts (XPath,
quotes, braces, etc.)
+ */
export function getElementCompletionProvider(dfdlFormatString: string) {
return vscode.languages.registerCompletionItemProvider(
'dfdl',
{
+ /**
+ * Provides completion items when newline is pressed in a DFDL document.
+ * Determines which child elements are valid based on the current XML
context.
+ *
+ * **Context Detection Flow:**
+ * 1. Check if cursor is in a valid context (not XPath, quotes, braces,
etc.)
+ * 2. Find the nearest open parent element
+ * 3. Check if there are any unclosed tags that need completion
+ * 4. Determine which element is nearest to the cursor position
+ * 5. Return appropriate child elements for that parent
+ *
+ * @param document - The active text document
+ * @param position - The cursor position where newline was pressed
+ * @param token - Cancellation token for async operations
+ * @param context - Completion context including trigger character
+ * @returns Array of completion items or undefined
+ */
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
@@ -45,6 +74,9 @@ export function
getElementCompletionProvider(dfdlFormatString: string) {
context: vscode.CompletionContext
) {
const triggerChar = context.triggerCharacter
+
+ // **GUARD CLAUSES**: Only provide completions in valid XML contexts
+ // Prevent completion inside XPath expressions, braces, quotes, after
equals, or at tag ends
if (
!checkBraceOpen(document, position) &&
!cursorWithinBraces(document, position) &&
@@ -58,24 +90,27 @@ export function
getElementCompletionProvider(dfdlFormatString: string) {
let triggerText = document.lineAt(triggerLine).text
let itemsOnLine = getItemsOnLineCount(triggerText)
let nearestOpenItem = nearestOpen(document, position)
+
+ // If we found an open item with a namespace, use that namespace
if (nearestOpenItem.itemNS != 'none') {
nsPrefix = nearestOpenItem.itemNS
}
let lastCloseSymbol = triggerText.lastIndexOf('>')
let firstOpenSymbol = triggerText.indexOf('<')
-
let missingCloseTag = checkMissingCloseTag(
document,
position,
nsPrefix
)
-
+ // **EARLY RETURN**: If inside an open element with no missing close
tags, don't suggest
if (
!nearestOpenItem.itemName.includes('none') &&
missingCloseTag == 'none'
) {
return undefined
}
+
+ // **EARLY RETURN**: For multi-item lines at specific positions,
don't suggest
if (
missingCloseTag === 'none' &&
itemsOnLine > 1 &&
@@ -84,9 +119,9 @@ export function
getElementCompletionProvider(dfdlFormatString: string) {
) {
return undefined
}
-
+ // Scan document for user-defined variables to include in completions
let definedVariables = getDefinedVariables(document)
-
+ // Find which tag is nearest to the cursor position
let [tagNearestTrigger, tagPosition] = getTagNearestTrigger(
document,
position,
@@ -96,7 +131,7 @@ export function
getElementCompletionProvider(dfdlFormatString: string) {
itemsOnLine,
nsPrefix
)
-
+ // Return appropriate child elements for the parent context
return nearestOpenTagChildElements(
document,
position,
@@ -108,10 +143,13 @@ export function
getElementCompletionProvider(dfdlFormatString: string) {
}
},
},
- '\n'
+ '\n' // Triggered on newline
)
}
-
+/**
+ * Registers a simpler element completion provider for TDML files.
+ * TDML has less complex requirements than DFDL schemas.
+ */
export function getTDMLElementCompletionProvider(tdmlFormatString: string) {
return vscode.languages.registerCompletionItemProvider('tdml', {
provideCompletionItems(
@@ -120,6 +158,7 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
token: vscode.CancellationToken,
context: vscode.CompletionContext
) {
+ // **SIMPLER GUARD CLAUSES**: TDML uses less strict validation
if (
checkBraceOpen(document, position) ||
cursorWithinBraces(document, position) ||
@@ -129,7 +168,6 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
) {
return undefined
}
-
let nsPrefix = getNsPrefix(document, position)
let [triggerLine, triggerPos] = [position.line, position.character]
let triggerText = document.lineAt(triggerLine).text
@@ -139,9 +177,7 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
let nearestOpenItem = xmlItem.itemName
let lastCloseSymbol = triggerText.lastIndexOf('>')
let firstOpenSymbol = triggerText.indexOf('<')
-
let missingCloseTag = checkMissingCloseTag(document, position, nsPrefix)
-
if (nearestOpenItem.includes('none')) {
if (missingCloseTag !== 'none') {
return undefined
@@ -153,9 +189,7 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
) {
return undefined
}
-
let definedVariables = getDefinedVariables(document)
-
let [tagNearestTrigger, tagPosition] = getTagNearestTrigger(
document,
position,
@@ -165,7 +199,6 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
itemsOnLine,
nsPrefix
)
-
return nearestOpenTagChildElements(
document,
position,
@@ -178,7 +211,16 @@ export function
getTDMLElementCompletionProvider(tdmlFormatString: string) {
},
})
}
-
+/**
+ * Creates completion items from the elementItems data structure.
+ * Filters items based on which elements are valid in the current context.
+ *
+ * @param itemsToUse - Array of element names that are valid in this context
+ * @param preVal - Prefix value to prepend (usually empty for elements)
+ * @param definedVariables - Comma-separated list of variable names for
dfdl:setVariable completion
+ * @param nsPrefix - Namespace prefix to use (xs:, dfdl:, etc.)
+ * @returns Array of VS Code completion items
+ */
function getElementCompletionItems(
itemsToUse: string[],
preVal: string = '',
@@ -186,10 +228,12 @@ function getElementCompletionItems(
nsPrefix: string
) {
let compItems: vscode.CompletionItem[] = []
-
+ // Iterate through all available element completions
elementCompletion(definedVariables, nsPrefix).items.forEach((e) => {
for (let i = 0; i < itemsToUse.length; ++i) {
+ // Check if this item matches one of the valid elements for this context
if (e.item.includes(itemsToUse[i])) {
+ // Handle dfdl-prefixed items specially
if (
(e.item.includes('dfdl:') && itemsToUse[i].includes('dfdl:')) ||
!e.item.includes('dfdl')
@@ -200,37 +244,65 @@ function getElementCompletionItems(
}
}
})
-
return compItems
}
-
+/**
+ * Scans the document to find all dfdl:defineVariable declarations.
+ * Extracts variable names to populate the dropdown for dfdl:setVariable
completion.
+ *
+ * @param document - The VS Code text document to scan
+ * @returns Comma-separated string of variable names, or empty string if none
found
+ */
function getDefinedVariables(document: vscode.TextDocument) {
let additionalTypes = ''
let lineNum = 0
let itemCnt = 0
const lineCount = document.lineCount
-
+ // Scan entire document line by line
while (lineNum !== lineCount) {
const triggerText = document
.lineAt(lineNum)
.text.substring(0, document.lineAt(lineNum).range.end.character)
-
+ // Look for variable definitions
if (triggerText.includes('dfdl:defineVariable name=')) {
let startPos = triggerText.indexOf('"', 0)
let endPos = triggerText.indexOf('"', startPos + 1)
let newType = triggerText.substring(startPos + 1, endPos)
-
+ // Build comma-separated list of variable names
additionalTypes =
itemCnt === 0 ? newType : String(additionalTypes + ',' + newType)
++itemCnt
}
-
++lineNum
}
-
return additionalTypes
}
-
+/**
+ * Determines which child elements are valid for a given parent element.
+ * This is the core logic for context-aware element completion in DFDL schemas.
+ *
+ * **DFDL Schema Hierarchy Rules:**
+ * - `element` can contain: `complexType`, `simpleType`, `annotation`
+ * - `sequence` can contain: `element`, `sequence`, `choice`, `annotation`
+ * - `choice` can contain: `element`, `sequence`, `group`, `annotation`
+ * - `complexType` can contain: `sequence`, `group`, `choice`, `annotation`
+ * - `simpleType` can contain: `annotation`, `restriction`
+ * - `annotation` → `appinfo` → DFDL format definitions
+ * - `schema` root can contain: `sequence`, `element`, `choice`, `group`,
`complexType`, `simpleType`, `annotation`, `include`, `import`
+ *
+ * **Special Contexts:**
+ * - Inside `appinfo`: Provides DFDL-specific elements based on parent of
annotation
+ * - `assert`/`discriminator`: Suggests CDATA and {} for expressions
+ * - Variable-related tags: Provide specific DFDL variable elements
+ *
+ * @param document - The VS Code text document
+ * @param position - The cursor position
+ * @param tagNearestTrigger - The parent element name
+ * @param tagPosition - Position of the parent element
+ * @param definedVariables - Comma-separated variable names for setVariable
completion
+ * @param nsPrefix - Namespace prefix (xs:, dfdl:, etc.)
+ * @returns Array of completion items or undefined
+ */
function nearestOpenTagChildElements(
document: vscode.TextDocument,
position: vscode.Position,
@@ -239,6 +311,7 @@ function nearestOpenTagChildElements(
definedVariables: string,
nsPrefix: string
) {
+ // **MAIN SWITCH**: Determine valid child elements based on parent tag
switch (tagNearestTrigger) {
case 'element':
return getElementCompletionItems(
@@ -283,25 +356,35 @@ function nearestOpenTagChildElements(
nsPrefix
)
case 'annotation':
+ // Annotation can only contain appinfo
return getElementCompletionItems(['appinfo'], '', '', nsPrefix)
case 'appinfo':
+ // Appinfo context requires special handling - need to find parent of
annotation
let triggerText = document.lineAt(tagPosition.line).text
let iCount = getItemsOnLineCount(triggerText)
+
+ // Adjust position if needed for multi-line tags
const newPosition =
iCount < 2
? new vscode.Position(tagPosition.line - 1, tagPosition.character)
: tagPosition
+
+ // Find the parent element that contains this annotation
let [pElement, pPosition] = getAnnotationParent(
document,
newPosition,
nsPrefix
)
+
+ // Get attributes of the parent element
let attributeNames: string[] = getAttributeNames(
document,
pPosition,
nsPrefix,
pElement
)
+
+ // Provide DFDL-specific elements based on parent type
switch (pElement) {
case 'schema':
return getElementCompletionItems(
@@ -354,6 +437,7 @@ function nearestOpenTagChildElements(
nsPrefix
)
case 'group':
+ // Group behavior depends on whether it has a 'ref' attribute
if (attributeNames.includes('ref')) {
return getElementCompletionItems(
[
@@ -397,8 +481,10 @@ function nearestOpenTagChildElements(
return undefined
}
case 'assert':
+ // Assert can contain CDATA or expressions
return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
case 'discriminator':
+ // Discriminator can contain CDATA or expressions
return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
case 'defineFormat':
return getElementCompletionItems(['dfdl:format'], '', '', nsPrefix)
@@ -411,6 +497,7 @@ function nearestOpenTagChildElements(
case 'import':
return getElementCompletionItems([''], '', '', nsPrefix)
case 'schema':
+ // Root schema can contain many top-level elements
return getElementCompletionItems(
[
'sequence',
@@ -429,14 +516,35 @@ function nearestOpenTagChildElements(
nsPrefix
)
case 'xml version':
+ // After XML declaration, suggest schema
return getElementCompletionItems(['schema'], '', '', '')
case 'emptySchema':
+ // Empty document, suggest XML declaration
return getElementCompletionItems(['xml version'], '', '', '')
default:
return undefined
}
}
-
+/**
+ * Finds the parent element of an annotation tag.
+ * In DFDL schemas, the content of `appinfo` depends on what element the
annotation is attached to.
+ * This function traverses up to find that parent element.
+ *
+ * **Example:**
+ * ```xml
+ * <xs:element name="example">
+ * <xs:annotation>
+ * <xs:appinfo>|cursor here|</xs:appinfo>
+ * </xs:annotation>
+ * </xs:element>
+ * ```
+ * Returns `['element', positionOfElement]`
+ *
+ * @param document - The VS Code text document
+ * @param tagPosition - Position within the annotation/appinfo
+ * @param nsPrefix - Namespace prefix
+ * @returns Tuple: [parentElementName, parentElementPosition]
+ */
export function getAnnotationParent(
document: vscode.TextDocument,
tagPosition: vscode.Position,
@@ -456,9 +564,11 @@ export function getAnnotationParent(
nsPrefix
)
pElement = nElement
- //get parent of annotation tag
+
+ // If we found an annotation, we need to go up one more level to get the
actual parent
if (pElement === 'annotation') {
if (iCount < 2) {
+ // Adjust position for multi-line cases
newPosition = new vscode.Position(
newPosition.line - 1,
newPosition.character
@@ -479,7 +589,31 @@ export function getAnnotationParent(
}
return [pElement, pPosition]
}
-
+/**
+ * Finds the nearest tag to the cursor position that should trigger completion.
+ * This is a complex function that handles single-line and multi-line tag
scenarios.
+ *
+ * **Algorithm:**
+ * 1. For empty documents, returns special 'emptySchema' indicator
+ * 2. For multi-item lines: checks cursor position relative to tags
+ * 3. For single-item lines: walks up the document tree to find the
appropriate parent
+ * 4. Uses `nearestTag()` and `getCloseTag()` to determine tag relationships
+ * 5. Handles edge cases where tags are unclosed or nested
+ *
+ * **Key Logic:**
+ * - On multi-tag lines, ensures cursor is positioned correctly between tags
+ * - Tracks tag closure state to find the nearest open tag
+ * - Adjusts search position based on nesting levels
+ *
+ * @param document - The VS Code text document
+ * @param position - The cursor position
+ * @param triggerText - Text of the current line
+ * @param triggerLine - Current line number
+ * @param triggerPos - Current character position
+ * @param itemsOnLine - Count of XML items on the line
+ * @param nsPrefix - Namespace prefix
+ * @returns Tuple: [nearestTagName, tagPosition]
+ */
export function getTagNearestTrigger(
document: vscode.TextDocument,
position: vscode.Position,
@@ -491,7 +625,7 @@ export function getTagNearestTrigger(
): [string, vscode.Position] {
let [startLine, startPos] = [triggerLine, triggerPos]
let tagNearestTrigger = 'none'
-
+ // **SPECIAL CASE**: Empty document - return special indicator
if (
itemsOnLine === 0 &&
document.lineCount === 1 &&
@@ -499,7 +633,7 @@ export function getTagNearestTrigger(
) {
return ['emptySchema', position]
}
-
+ // **MAIN LOOP**: Continuously search for the nearest tag
while (true) {
let [foundTag, foundLine, foundPos] = nearestTag(
document,
@@ -508,7 +642,7 @@ export function getTagNearestTrigger(
startLine,
startPos
)
-
+ // **MULTI-ITEM LINE HANDLING**: Complex logic for lines with multiple tags
if (itemsOnLine > 1) {
const afterTriggerText = triggerText.substring(triggerPos)
const afterTriggerPos = afterTriggerText.indexOf('<') + triggerPos
@@ -518,7 +652,7 @@ export function getTagNearestTrigger(
const beforeTriggerTag = beforeTriggerText.substring(
lastOpenTagBeforeTriggerPos
)
-
+ // Verify cursor is in a valid position between tags (not inside a
closing tag)
if (
triggerPos === afterTriggerPos &&
triggerPos === beforeTriggerPos + 1 &&
@@ -528,9 +662,8 @@ export function getTagNearestTrigger(
return [tagNearestTrigger, new vscode.Position(foundLine, foundPos)]
}
}
-
startLine = foundLine
-
+ // Check if the found tag is closed
let [endTag, endTagLine, endTagPos] = getCloseTag(
document,
position,
@@ -539,20 +672,20 @@ export function getTagNearestTrigger(
foundLine,
foundPos
)
-
+ // **MULTI-ITEM LINE LOGIC**: Ensure we're targeting the right tag
if (itemsOnLine > 1 && foundLine === triggerLine) {
if (foundTag === endTag && endTagPos >= triggerPos) {
tagNearestTrigger = foundTag
return [tagNearestTrigger, new vscode.Position(foundLine, foundPos)]
}
-
+ // Tag is closed, adjust search position
if (endTag === 'none') {
startLine = foundLine - 1
} else {
startPos = foundPos - 1
}
}
-
+ // **SINGLE-ITEM LINE LOGIC**: Walk up the tree to find appropriate parent
if (itemsOnLine < 2) {
if (
(foundTag === endTag && endTagLine >= triggerLine) ||
@@ -561,7 +694,6 @@ export function getTagNearestTrigger(
tagNearestTrigger = foundTag
return [tagNearestTrigger, new vscode.Position(foundLine, foundPos)]
}
-
startLine = foundLine - 1
}
}
diff --git a/src/language/providers/intellisense/elementItems.ts
b/src/language/providers/intellisense/elementItems.ts
index d44732b..63d2aa5 100644
--- a/src/language/providers/intellisense/elementItems.ts
+++ b/src/language/providers/intellisense/elementItems.ts
@@ -14,7 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+/**
+ * Element Completion Data and Factory Function
+ *
+ * This module exports the element completion data used by
elementCompletion.ts.
+ * It provides a complete catalog of XML/DFDL elements that can be suggested
+ * in different schema contexts, along with their snippet templates and
documentation.
+ *
+ * **Dynamic Behavior:**
+ * - The `elementCompletion` function generates completion items dynamically
+ * - It accepts `definedVariables` to populate the dfdl:setVariable dropdown
+ * - It accepts `nsPrefix` to customize snippets with the correct namespace
+ *
+ * **Snippet Features:**
+ * - Tab stops ($1, $2, $0) for navigation
+ * - Choice placeholders (${1|option1,option2|}) for enumerated values
+ * - Dynamic variable insertion using string concatenation
+ */
// prettier-ignore
export const elementCompletion = (definedVariables, nsPrefix) => {
return {
@@ -22,234 +38,240 @@ export const elementCompletion = (definedVariables,
nsPrefix) => {
{
item: 'xml version',
snippetString: '<?xml version="1.0" encoding="UTF-8"?>\n$0',
+ markdownString: 'XML declaration with UTF-8 encoding'
},
{
item: nsPrefix + 'schema',
- snippetString: '<${1|\0,xs:,xsd:|}$2' + 'schema
xmlns:xs="http://www.w3.org/2001/xmlSchema"\n\t\txmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"\n\t\txmlns:daf="urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:ext"\n\t\txmlns:fn="http:/www.w3.org/2005/xpath-functions"\nelementFormDefault="unqualified"$0',
+ snippetString: '<${1|' + '\0' + ',xs:,xsd:|}$2' + 'schema
xmlns:xs="http://www.w3.org/2001/xmlSchema"\n\t\txmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"\n\t\txmlns:daf="urn:ogf:dfdl:2013:imp:daffodil.apache.org:2018:ext"\n\t\txmlns:fn="http:/www.w3.org/2005/xpath-functions"\nelementFormDefault="unqualified"$0',
+ markdownString: 'Root schema element with standard DFDL namespace
declarations'
},
{
item: nsPrefix + 'element',
snippetString: '<' + nsPrefix + 'element$0',
- markdownString: 'Defines an xs element',
+ markdownString: 'Defines an xs element'
},
{
item: nsPrefix + 'element name',
snippetString: '<' + nsPrefix + 'element name="$1"$0',
- markdownString: 'Defines an xs element',
+ markdownString: 'Defines an xs element with name attribute'
},
{
item: nsPrefix + 'element ref',
snippetString: '<' + nsPrefix + 'element ref="$1"$0',
- markdownString: 'Defines a reference to a declared element',
+ markdownString: 'Defines a reference to a declared element'
},
{
item: nsPrefix + 'group',
snippetString: '<' + nsPrefix + 'group "$1">\n\t$0\n</' + nsPrefix +
'group>',
- markdownString: 'Defines a named model group to be reused later',
+ markdownString: 'Defines a named model group to be reused later'
},
{
item: nsPrefix + 'group name',
snippetString: '<' + nsPrefix + 'group name = "$1">\n\t$0\n</' +
nsPrefix + 'group>',
- markdownString: 'Defines a named model group to be reused later',
+ markdownString: 'Defines a named model group'
},
{
item: nsPrefix + 'group ref',
snippetString: '<' + nsPrefix + 'group ref="$1"$0',
- markdownString: 'Defines a reference to a group declaration',
+ markdownString: 'Defines a reference to a group declaration'
},
{
item: 'dfdl:assert',
snippetString: '<dfdl:assert $0',
- markdownString: 'Used to assert truths about a DFDL model',
+ markdownString: 'Used to assert truths about a DFDL model'
},
{
item: 'dfdl:discriminator',
snippetString: '<dfdl:discriminator $0',
- markdownString: 'Used during parsing to resolve points or
uncertainity, remove ambiguity during speculative parsing, improve diagnostic
behavior',
+ markdownString: 'Used during parsing to resolve points of uncertainty
during speculative parsing'
},
{
item: 'dfdl:format',
snippetString: '<dfdl:format $0',
- markdownString: 'Defines the physical data format properties for
multiple DFDL schema constructs',
+ markdownString: 'Defines physical data format properties for DFDL
constructs'
},
{
item: nsPrefix + 'annotation',
snippetString: '<' + nsPrefix + 'annotation>\n\t$0\n\</' + nsPrefix +
'annotation>',
+ markdownString: 'Container for DFDL annotation information'
},
{
item: nsPrefix + 'appinfo',
snippetString: '<' + nsPrefix + 'appinfo
source="http://www.ogf.org/dfdl/">\n\t$0\n</' + nsPrefix + 'appinfo>',
+ markdownString: 'Contains DFDL format definitions'
},
{
item: nsPrefix + 'complexType',
snippetString: '<' + nsPrefix + 'complexType>\n\t$0\n</' + nsPrefix +
'complexType>',
- markdownString: 'Defines a complex type definition',
+ markdownString: 'Defines a complex type definition'
},
{
item: nsPrefix + 'complexType name',
snippetString: '<' + nsPrefix + 'complexType name="$1">\n\t$0\n</' +
nsPrefix + 'complexType>',
- markdownString: 'Defines a complex type definition',
+ markdownString: 'Defines a named complex type'
},
{
item: nsPrefix + 'simpleType',
snippetString: '<' + nsPrefix + 'simpleType$1>\n\t$0\n</' + nsPrefix +
'simpleType>',
- markdownString: 'Defines a simple type definition',
+ markdownString: 'Defines a simple type definition'
},
{
item: nsPrefix + 'simpleType name',
snippetString: '<' + nsPrefix + 'simpleType name="$1"$0',
- markdownString: 'Defines simple type definition',
+ markdownString: 'Defines a named simple type'
},
{
item: nsPrefix + 'sequence',
snippetString: '<' + nsPrefix + 'sequence',
- markdownString: 'Specifies that the child elements must appear in a
sequence',
+ markdownString: 'Specifies child elements must appear in sequence'
},
{
item: nsPrefix + 'choice',
snippetString: '<' + nsPrefix + 'choice',
- markdownString: 'Define group of mutually exclusive elements that
resolve points of uncertainty that cannot be resolved by speculative parsing',
+ markdownString: 'Defines mutually exclusive elements'
},
{
item: 'dfdl:newVariableInstance',
snippetString: '<dfdl:newVariableInstance ref="$1"$0',
- markdownString: 'Defines the name, type, and optional default value
for the variable'
+ markdownString: 'Creates a new instance of a defined variable'
},
{
item: 'dfdl:defineVariable',
snippetString: '<dfdl:defineVariable "$1"$0',
- markdownString: 'Defines the name, type, and optionally default value
for the variable.',
+ markdownString: 'Defines a variable name, type, and optional default
value'
},
{
item: 'dfdl:defineVariable name',
snippetString: '<dfdl:defineVariable name="$1"$0',
- markdownString: 'Defines the name, type, and optionally default value
for the variable.',
+ markdownString: 'Defines a named variable with type and optional
default'
},
{
item: 'dfdl:setVariable',
+ // **DYNAMIC SNIPPET**: Uses definedVariables parameter to populate
ref attribute choices
snippetString: '<dfdl:setVariable ref="${1|' + definedVariables +
'"|}, value="$2"$0',
- markdownString: 'Sets the value of a variable whose declaration is in
scope',
+ markdownString: 'Sets the value of a variable in scope'
},
{
item: 'dfdl:defineFormat',
snippetString: '<dfdl:defineFormat
name="$1">\n\t$2\n</dfdl:defineFormat>$0',
- markdownString: 'Defines a named reusable format definition',
+ markdownString: 'Defines a reusable format definition'
},
{
item: 'dfdl:defineEscapeScheme',
snippetString: '<dfdl:defineEscapeScheme name=$1
>\n\t$0</dfdl:defineEscapeScheme>',
- markdownString: 'Defines a named, reusable escapeScheme',
+ markdownString: 'Defines a reusable escape scheme'
},
{
item: 'dfdl:escapeScheme',
snippetString: '<dfdl:escapeScheme $0',
- markdownString: 'Allows a common set of properties to be defined that
can be reused',
+ markdownString: 'References a common set of reusable properties'
},
{
item: 'dfdl:simpleType',
snippetString: '<dfdl:simpleType $1/>$0',
- markdownString: 'Defines the physical data format properties of an
xs:simpleType',
+ markdownString: 'Defines physical data format properties of
xs:simpleType'
},
{
item: 'dfdl:element',
snippetString: '<dfdl:element $1/>$0',
- markdownString: 'Defines the physical data format properties of an
xs:element',
+ markdownString: 'Defines physical data format properties of xs:element'
},
{
item: 'dfdl:sequence',
snippetString: '<dfdl:sequence $1/>$0',
- markdownString: 'Defines the physical data format properties of an
xs:sequence group',
+ markdownString: 'Defines physical data format properties of
xs:sequence'
},
{
item: 'dfdl:group',
snippetString: '<dfdl:group $1/>$0',
- markdownString: 'Defines the physical data format properties of an
xs:group reference',
+ markdownString: 'Defines physical data format properties of xs:group'
},
{
item: 'dfdl:choice',
snippetString: '<dfdl:choice $1/>$0',
- markdownString: 'Defines the physical data format properties of an
xs:choice group',
+ markdownString: 'Defines physical data format properties of xs:choice'
},
{
item: 'dfdl:property',
snippetString: '<dfdl:property name="$1">\n\t$2\n</dfdl:property>$0',
- markdownString: 'Used in the syntax of format annotations',
+ markdownString: 'Used in format annotations'
},
{
item: 'restriction',
- // use the "xs:" prefix for primitive types to differentiate them from
custom simple types
+ // **CHOICE PLACEHOLDER**: Provides dropdown of common XSD primitive
types
snippetString: '<' + nsPrefix + 'restriction
base="${1|xs:string,xs:decimal,xs:float,xs:double,xs:integer,xs:nonNegativeInteger,xs:int,xs:unsignedInt,xs:short,xs:unsignedShort,xs:long,xs:unsignedLong,xs:byte,xs:unsignedByte,xs:hexBinary,xs:boolean|}"$0',
- markdownString: 'Specify the base type the element is restricted to',
+ markdownString: 'Specifies base type for restriction'
},
{
item: 'minInclusive',
snippetString: '<' + nsPrefix + 'minInclusive value="$1"/>$0',
- markdownString: 'Used to check the validity of an element'
+ markdownString: 'Validates element has minimum inclusive value'
},
{
item: 'minExclusive',
snippetString: '<' + nsPrefix + 'minExclusive value="$1"/>$0',
- markdownString: 'Used to check the validity of an element'
+ markdownString: 'Validates element has minimum exclusive value'
},
{
item: 'maxInclusive',
snippetString: '<' + nsPrefix + 'maxInclusive value="$1"/>$0',
- markdownString: 'Used to check the validity of an element'
+ markdownString: 'Validates element has maximum inclusive value'
},
{
item: 'maxExclusive',
snippetString: '<' + nsPrefix + 'maxExclusive value="$1"/>$0',
- markdownString: 'Used to check the validity of an element'
+ markdownString: 'Validates element has maximum exclusive value'
},
{
item: 'pattern',
snippetString: '<' + nsPrefix + 'pattern value="$1"/>$0',
- markdownString: 'Used to derive new simple types by specifying a
regular expression against which values of the type are compared'
+ markdownString: 'Restricts type with regular expression pattern'
},
{
item: 'totalDigits',
snippetString: '<' + nsPrefix + 'totalDigits value="$1"/>$0',
- markdownString: 'Indicates the maximum allowed value for the number of
digits'
+ markdownString: 'Restricts maximum number of digits'
},
{
item: 'fractionDigits',
snippetString: '<' + nsPrefix + 'fractionDigits value="$1"/>$0',
- markdownString: 'Indicates the maximum number of digits in the
fractional part'
+ markdownString: 'Restricts maximum digits in fractional part'
},
{
item: 'enumeration',
snippetString: '<' + nsPrefix + 'enumeration value="$1"/>$0',
- markdownString: 'Used to restrict a datatype to a finite set of values'
+ markdownString: 'Restricts type to finite set of values'
},
{
item: nsPrefix + 'include',
snippetString: '<' + nsPrefix + 'include "$1"/>$0',
- markdownString: 'Used to add all the components of an included schema'
+ markdownString: 'Includes components from another schema'
},
{
item: 'documentation',
- snippetString: '<' + nsPrefix +
'documentation>\n\t$1\n</documentation>$0'
+ snippetString: '<' + nsPrefix +
'documentation>\n\t$1\n</documentation>$0',
+ markdownString: 'Contains human-readable documentation'
},
{
item: nsPrefix + 'import',
snippetString: '<' + nsPrefix + 'import "$1"/>$0',
- markdownString: 'Used to add all the components of an included schema'
+ markdownString: 'Imports components from another namespace'
},
{
item: '<[CDATA[]]>',
snippetString: '<[CDATA[$1]]>$0',
- markdownString: ''
+ markdownString: 'CDATA section (alternative syntax)'
},
{
item: '<![CDATA[]]>',
snippetString: '<![CDATA[$1]]>$0',
- markdownString: ''
+ markdownString: 'CDATA section for character data'
},
{
item: '{}',
snippetString: '{$1}$0',
- markdownString: ''
- },
+ markdownString: 'DFDL expression wrapper'
+ }
],
}
}