blob: d3a7c0b45ac98c59d12d355aae8cc6e9def1cb8d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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 {
checkBraceOpen,
getXsdNsPrefix,
isInXPath,
nearestOpen,
createCompletionItem,
getCommonItems,
nearestTag,
getItemsOnLineCount,
cursorWithinBraces,
cursorWithinQuotes,
cursorAfterEquals,
} from './utils'
import { elementCompletion } from './intellisense/elementItems'
export function getElementCompletionProvider(dfdlFormatString: string) {
return vscode.languages.registerCompletionItemProvider('dfdl', {
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken,
context: vscode.CompletionContext
) {
if (
checkBraceOpen(document, position) ||
cursorWithinBraces(document, position) ||
cursorWithinQuotes(document, position) ||
cursorAfterEquals(document, position) ||
isInXPath(document, position)
) {
return undefined
}
let nsPrefix = getXsdNsPrefix(document, position)
let [triggerLine, triggerPos] = [position.line, position.character]
let triggerText = document.lineAt(triggerLine).text
let itemsOnLine = getItemsOnLineCount(triggerText)
let nearestOpenItem = nearestOpen(document, position)
let lastCloseSymbol = triggerText.lastIndexOf('>')
let firstOpenSymbol = triggerText.indexOf('<')
let missingCloseTag = checkMissingCloseTag(document, position, nsPrefix)
if (nearestOpenItem.includes('none')) {
if (missingCloseTag !== 'none') {
return undefined
}
if (
missingCloseTag === 'none' &&
itemsOnLine > 1 &&
(triggerPos === lastCloseSymbol + 1 || triggerPos === firstOpenSymbol)
) {
return undefined
}
let definedVariables = getDefinedVariables(document)
let tagNearestTrigger = getTagNearestTrigger(
document,
position,
triggerText,
triggerLine,
triggerPos,
itemsOnLine,
nsPrefix
)
return checkTagNearestOpen(
document,
position,
tagNearestTrigger,
definedVariables,
nsPrefix
)
}
},
})
}
function getElementCompletionItems(
itemsToUse: string[],
preVal: string = '',
definedVariables: string = '',
nsPrefix: string
) {
let compItems: vscode.CompletionItem[] = getCommonItems(
itemsToUse,
preVal,
definedVariables,
nsPrefix
)
elementCompletion(definedVariables, nsPrefix).items.forEach((e) => {
for (let i = 0; i < itemsToUse.length; ++i) {
if (e.item.includes(itemsToUse[i])) {
if (
(e.item.includes('dfdl:') && itemsToUse[i].includes('dfdl:')) ||
!e.item.includes('dfdl')
) {
const completionItem = createCompletionItem(e, preVal, nsPrefix)
compItems.push(completionItem)
}
}
}
})
return compItems
}
function getDefinedVariables(document: vscode.TextDocument) {
let additionalTypes = ''
let lineNum = 0
let itemCnt = 0
const lineCount = document.lineCount
while (lineNum !== lineCount) {
const triggerText = document
.lineAt(lineNum)
.text.substring(0, document.lineAt(lineNum).range.end.character)
if (triggerText.includes('dfdl:defineVariable name=')) {
let startPos = triggerText.indexOf('"', 0)
let endPos = triggerText.indexOf('"', startPos + 1)
let newType = triggerText.substring(startPos + 1, endPos)
additionalTypes =
itemCnt === 0 ? newType : String(additionalTypes + ',' + newType)
++itemCnt
}
++lineNum
}
return additionalTypes
}
function checkTagNearestOpen(
document: vscode.TextDocument,
position: vscode.Position,
tagNearestTrigger: string,
definedVariables: string,
nsPrefix: string
) {
switch (tagNearestTrigger) {
case 'element':
return getElementCompletionItems(
['complexType', 'simpleType', 'annotation'],
'',
definedVariables,
nsPrefix
)
case 'sequence':
return getElementCompletionItems(
['element', 'sequence', 'choice', 'annotation'],
'',
'',
nsPrefix
)
case 'choice':
return getElementCompletionItems(
['element', 'sequence', 'group', 'annotation'],
'',
'',
nsPrefix
)
case 'group':
return getElementCompletionItems(
['sequence', 'annotation'],
'',
'',
nsPrefix
)
case 'complexType':
return getElementCompletionItems(
['sequence', 'group', 'choice'],
'',
'',
nsPrefix
)
case 'simpleType':
return getElementCompletionItems(
['annotation', 'restriction'],
'',
'',
nsPrefix
)
case 'restriction':
return getElementCompletionItems(
['maxInclusive', 'maxExclusive', 'minInclusive', 'minExclusive'],
'',
'',
nsPrefix
)
case 'annotation':
return getElementCompletionItems(['appinfo'], '', '', nsPrefix)
case 'appinfo':
return getElementCompletionItems(
[
'dfdl:assert',
'dfdl:discriminator',
'dfdl:defineFormat',
'dfdl:format',
'dfdl:defineVariable',
'dfdl:setVariable',
'dfdl:defineEscapeScheme',
'dfdl:element',
'dfdl:simpleType',
],
'',
'',
nsPrefix
)
case 'assert':
return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
case 'discriminator':
return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
case 'defineFormat':
return getElementCompletionItems(['format'], '', '', nsPrefix)
case 'schema':
return getElementCompletionItems(
[
'sequence',
'element',
'choice',
'group',
'complexType',
'simpleType',
'annotation',
],
'',
'',
nsPrefix
)
case 'xml version':
return getElementCompletionItems(['schema'], '', '', '')
case 'emptySchema':
return getElementCompletionItems(['xml version'], '', '', '')
default:
return undefined
}
}
export function getTagNearestTrigger(
document: vscode.TextDocument,
position: vscode.Position,
triggerText: string,
triggerLine: number,
triggerPos: number,
itemsOnLine: number,
nsPrefix: string
): string {
let [startLine, startPos] = [triggerLine, triggerPos]
let tagNearestTrigger = 'none'
if (
itemsOnLine === 0 &&
document.lineCount === 1 &&
position.character === 0
) {
return 'emptySchema'
}
while (true) {
let [foundTag, foundLine, foundPos] = nearestTag(
document,
position,
nsPrefix,
startLine,
startPos
)
if (itemsOnLine > 1) {
const afterTriggerText = triggerText.substring(triggerPos)
const afterTriggerPos = afterTriggerText.indexOf('<') + triggerPos
const beforeTriggerText = triggerText.substring(0, triggerPos)
const lastOpenTagBeforeTriggerPos = beforeTriggerText.lastIndexOf('<')
const beforeTriggerPos = beforeTriggerText.lastIndexOf('>')
const beforeTriggerTag = beforeTriggerText.substring(
lastOpenTagBeforeTriggerPos
)
if (
triggerPos === afterTriggerPos &&
triggerPos === beforeTriggerPos + 1 &&
!beforeTriggerTag.startsWith('</')
) {
tagNearestTrigger = foundTag
return tagNearestTrigger
}
}
startLine = foundLine
let [endTag, endTagLine, endTagPos] = getCloseTag(
document,
position,
nsPrefix,
foundTag,
foundLine,
foundPos
)
if (itemsOnLine > 1 && foundLine === triggerLine) {
if (foundTag === endTag && endTagPos >= triggerPos) {
tagNearestTrigger = foundTag
return tagNearestTrigger
}
if (endTag === 'none') {
startLine = foundLine - 1
} else {
startPos = foundPos - 1
}
}
if (itemsOnLine < 2) {
if (
(foundTag === endTag && endTagLine >= triggerLine) ||
endTag === 'xml version'
) {
tagNearestTrigger = foundTag
return tagNearestTrigger
}
startLine = foundLine - 1
}
}
}