src/language/providers/attributeValueCompletion.ts (164 lines of code) (raw):
/*
* 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 { checkBraceOpen, cursorWithinBraces, getNsPrefix } from './utils'
import { getDefinedTypes } from './attributeCompletion'
import {
attributeValues,
noChoiceAttributes,
} from './intellisense/attributeValueItems'
export function getAttributeValueCompletionProvider() {
return vscode.languages.registerCompletionItemProvider(
'dfdl',
{
async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
) {
if (
checkBraceOpen(document, position) ||
cursorWithinBraces(document, position)
) {
return undefined
}
const schemaPosition = new vscode.Position(0, 0)
const nsPrefix = getNsPrefix(document, schemaPosition)
let additionalItems = getDefinedTypes(document, nsPrefix)
let [attributeName, startPos, endPos] = getAttributeDetails(
document,
position
)
if (attributeName !== 'none' && !attributeName.includes('xmlns:')) {
let replaceValue = ''
if (startPos === endPos) {
replaceValue = ' '
}
if (attributeName.includes(':')) {
attributeName = attributeName.substring(
attributeName.indexOf(':') + 1
)
}
if (noChoiceAttributes.includes(attributeName)) {
return undefined
}
let startPosition = position.with(position.line, startPos)
let endPosition = position.with(position.line, endPos + 1)
let range = new vscode.Range(startPosition, endPosition)
await vscode.window.activeTextEditor?.edit((editBuilder) => {
editBuilder.replace(range, replaceValue)
})
attributeValues(attributeName, startPosition, additionalItems)
}
return undefined
},
},
' ' // triggered whenever a space is typed
)
}
export function getTDMLAttributeValueCompletionProvider() {
return vscode.languages.registerCompletionItemProvider(
'dfdl',
{
async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position
) {
if (
checkBraceOpen(document, position) ||
cursorWithinBraces(document, position)
) {
return undefined
}
const nsPrefix = getNsPrefix(document, position)
let additionalItems = getDefinedTypes(document, nsPrefix)
let [attributeName, startPos, endPos] = getAttributeDetails(
document,
position
)
if (attributeName !== 'none') {
let replaceValue = ''
if (startPos === endPos) {
replaceValue = ' '
}
if (attributeName.includes(':')) {
attributeName = attributeName.substring(
attributeName.indexOf(':') + 1
)
}
if (noChoiceAttributes.includes(attributeName)) {
return undefined
}
let startPosition = position.with(position.line, startPos)
let endPosition = position.with(position.line, endPos + 1)
let range = new vscode.Range(startPosition, endPosition)
await vscode.window.activeTextEditor?.edit((editBuilder) => {
editBuilder.replace(range, replaceValue)
})
attributeValues(attributeName, startPosition, additionalItems)
}
return undefined
},
},
' ' // triggered whenever a space is typed
)
}
function getAttributeDetails(
document: vscode.TextDocument,
position: vscode.Position
): [attributeName: string, valueStartPos: number, valueEndPos: number] {
const quoteChar: string[] = ["'", '"']
const triggerLine = position.line
const triggerPos = position.character
let currentLine = triggerLine
let currentPos = triggerPos
let endPos = -1
let currentText = document.lineAt(currentLine).text
let textBeforeTrigger = currentText.substring(0, triggerPos)
let attributeName = 'none'
let attributeStartPos = 0
while (
!currentText.includes("'") &&
!currentText.includes('"') &&
!currentText.includes('=') &&
!currentText.includes('<') &&
!currentText.includes('>') &&
currentLine > 0 &&
currentLine < document.lineCount
) {
currentText = document.lineAt(--currentLine).text
}
if (currentLine === 0 || currentLine === document.lineCount) {
return ['none', 0, 0]
}
if ((currentPos = textBeforeTrigger.lastIndexOf('=')) !== -1) {
if (triggerPos === currentPos + 1) {
attributeStartPos = textBeforeTrigger.lastIndexOf(' ') + 1
attributeName = textBeforeTrigger.substring(attributeStartPos, currentPos)
return [attributeName, currentPos + 1, currentPos + 1]
}
}
for (let i = 0; i < quoteChar.length; ++i) {
if (currentText.includes(quoteChar[i])) {
if (currentLine === triggerLine) {
currentPos = textBeforeTrigger.lastIndexOf(quoteChar[i])
if (
currentPos < triggerPos &&
textBeforeTrigger.lastIndexOf('=') === currentPos - 1
) {
textBeforeTrigger = textBeforeTrigger.substring(
0,
textBeforeTrigger.lastIndexOf('=')
)
endPos = currentText.indexOf(quoteChar[i], currentPos + 1)
attributeStartPos = textBeforeTrigger.lastIndexOf(' ')
attributeName = textBeforeTrigger.substring(
attributeStartPos + 1,
currentPos - 1
)
}
}
}
if (attributeName !== 'none') {
break
}
}
return [attributeName, currentPos, endPos]
}