src/language/providers/utils.ts (535 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 { commonCompletion } from './intellisense/commonItems'
import { isXPath } from '../semantics/dfdlExt'
const schemaPrefixRegEx = new RegExp('</?(|[^ ]+:)schema')
//List of high level dfdl element items
const items = [
'element',
'sequence',
'choice',
'group',
'simpleType',
'complexType',
'annotation',
'appinfo',
'assert',
'discriminator',
'defineFormat',
'format',
'newVariableInstance',
'defineVariable',
'setVariable',
'defineEscapeScheme',
'escapeScheme',
'dfdl:element',
'dfdl:simpleType',
'restriction',
'schema',
'xml version',
]
export function getItems() {
return items
}
// default namespace in the event that a namespace was not found
export const defaultXsdNsPrefix = ''
// dfdl namespace for dfdl format element in non dfdl tags
export const dfdlDefaultPrefix = 'dfdl:'
// Function to insert snippet to active editor
export function insertSnippet(snippetString: string, backpos: vscode.Position) {
vscode.window.activeTextEditor?.insertSnippet(
new vscode.SnippetString(snippetString),
backpos
)
}
export function lineCount(
document: vscode.TextDocument,
position: vscode.Position,
tag: string
) {
let lineNum = position.line
let lineCount = 0
const nsPrefix = getXsdNsPrefix(document, position)
while (lineNum !== 0) {
--lineNum
++lineCount
const triggerText = document.lineAt(lineNum).text
if (
triggerText.includes('<' + nsPrefix + tag) &&
!triggerText.includes('</' + nsPrefix + tag) &&
!triggerText.includes('/>')
) {
return lineCount
}
}
return lineCount
}
export function nearestOpen(
document: vscode.TextDocument,
position: vscode.Position
) {
if (document.lineCount === 1 && position.character === 0) {
return 'none'
}
const nsPrefix = getXsdNsPrefix(document, position)
for (let i = 0; i < items.length; ++i) {
if (checkTagOpen(document, position, nsPrefix, items[i])) {
return items[i]
}
}
return 'none'
}
export function nearestTag(
document: vscode.TextDocument,
position: vscode.Position,
nsPrefix: string,
startLine: number,
startPos: number
): [string, number, number] {
const triggerLine = position.line
const origPrefix = nsPrefix
let lineNum = startLine
const triggerText = document.lineAt(triggerLine).text
const itemsOnLine = getItemsOnLineCount(document.lineAt(lineNum).text)
let tagPos = triggerText.indexOf('<')
let endPos = triggerText.lastIndexOf('>')
if (
itemsOnLine > 1 &&
startPos !== tagPos &&
startPos < endPos &&
endPos != startPos
) {
let textBeforeTrigger = triggerText.substring(0, startPos)
let prevTagPos = 0
while (prevTagPos > -1) {
prevTagPos = textBeforeTrigger.lastIndexOf('<')
let tag = textBeforeTrigger.substring(prevTagPos)
if (
!textBeforeTrigger.includes('</') &&
!textBeforeTrigger.includes('/>')
) {
for (let i = 0; i < items.length; ++i) {
nsPrefix = getItemPrefix(items[i], origPrefix)
if (tag.includes('<' + nsPrefix + items[i])) {
return [items[i], startLine, prevTagPos]
}
}
}
textBeforeTrigger = textBeforeTrigger.substring(0, prevTagPos)
}
} else {
if (
startLine === triggerLine &&
(tagPos === startPos || triggerText.trim() === '')
) {
--lineNum
}
while (lineNum > -1 && lineNum < document.lineCount) {
let currentText = document.lineAt(lineNum).text
if (getItemsOnLineCount(currentText) < 2) {
if (!currentText.includes('/>')) {
for (let i = 0; i < items.length; ++i) {
nsPrefix = getItemPrefix(items[i], origPrefix)
if (
!currentText.includes('</') &&
(currentText.includes('<' + nsPrefix + items[i]) ||
(lineNum === 0 && currentText.includes(items[i])))
) {
return [items[i], lineNum, startPos]
}
if (
currentText.includes('<' + nsPrefix + items[i]) &&
currentText.includes('</' + nsPrefix + items[i]) &&
position.character > currentText.indexOf('>') &&
position.character <= currentText.indexOf('</')
) {
return [items[i], lineNum, startPos]
}
}
}
}
--lineNum
}
}
return ['none', 0, 0]
}
export function checkTagOpen(
document: vscode.TextDocument,
position: vscode.Position,
nsPrefix: string,
tag: string
) {
nsPrefix = getItemPrefix(tag, nsPrefix)
let triggerLine = position.line
let triggerText = document.lineAt(triggerLine).text
let itemsOnLine = getItemsOnLineCount(triggerText)
let isMultiLineTag = false
let origTriggerText = triggerText
let origTriggerLine = triggerLine
const triggerPos = position.character
const textBeforeTrigger = triggerText.substring(0, triggerPos)
while (itemsOnLine < 2 && !triggerText.trim().startsWith('<')) {
triggerText = document.lineAt(--triggerLine).text
}
if (!(triggerText.endsWith('>') && triggerText.includes('<'))) {
isMultiLineTag = true
}
let tagPos = textBeforeTrigger.lastIndexOf('<' + nsPrefix + tag)
const nextTagPos = triggerText.indexOf('<', tagPos + 1)
let tagEndPos = triggerText.indexOf('>', tagPos)
if (tagPos > -1 && itemsOnLine > 1) {
if (
triggerPos > tagPos &&
((triggerPos <= tagEndPos &&
(nextTagPos > tagEndPos || nextTagPos === -1)) ||
tagEndPos === -1)
) {
return true
}
}
while (origTriggerText.trim() === '') {
origTriggerText = document.lineAt(--origTriggerLine).text
}
tagPos = triggerText.indexOf('<' + nsPrefix + tag)
if (itemsOnLine < 2 && tagPos > -1) {
if (triggerText !== origTriggerText) {
tagEndPos = origTriggerText.indexOf('>')
}
if (
(triggerPos > tagPos &&
triggerPos <= tagEndPos &&
triggerLine === position.line) ||
(origTriggerLine == position.line &&
triggerPos <= tagEndPos &&
triggerPos > tagPos) ||
position.line < origTriggerLine
) {
return true
}
}
if (!isMultiLineTag || tagPos === -1) {
return false
}
//if this tag is part of a multi line set of annotations return true
//else this tag is not open return false
return checkMultiLineTag(
document,
position,
itemsOnLine,
nsPrefix,
tagPos,
triggerLine,
tag
)
}
export function getItemPrefix(item: string, nsPrefix: string) {
let itemPrefix = nsPrefix
if (
item === 'assert' ||
item === 'discriminator' ||
item === 'defineFormat' ||
item === 'format' ||
item.includes('Variable') ||
item.includes('scape')
) {
itemPrefix = 'dfdl:'
}
if (item === 'xml version') {
itemPrefix = '?'
}
if (item === 'dfdl:element' || item === 'dfdl:simpleType') {
itemPrefix = ''
}
return itemPrefix
}
export function checkMultiLineTag(
document: vscode.TextDocument,
position: vscode.Position,
itemsOnLine: number,
nsPrefix: string,
tagPos: number,
tagLine: number,
tag: string
) {
if (itemsOnLine > 1) {
return false
}
let currentLine = position.line
const origText = document.lineAt(currentLine).text
let currentText = origText
//the current line doesn't have the self close symbol
if (!currentText.endsWith('/>')) {
while (currentText.trim() === '' || !currentText.includes('<')) {
--currentLine
currentText = document.lineAt(currentLine).text
}
if (
currentText.indexOf('<' + nsPrefix + tag) !== -1 &&
currentText.indexOf('>') === -1 &&
currentText.indexOf('<' + nsPrefix + tag) &&
currentLine <= position.line &&
(origText.indexOf('>') > position.character ||
origText.indexOf('>') === -1)
) {
return true
}
}
if (currentText.endsWith('/>')) {
let triggerPos = position.character
let tagEndPos = currentText.indexOf('/>')
let triggerLine = position.line
if (
(triggerLine === currentLine && triggerPos < tagEndPos) ||
(triggerLine === tagLine && triggerPos > tagPos && tagPos !== -1) ||
triggerLine < currentLine
) {
return true
}
}
return false
}
//returns an empty value or a prefix plus a colon
export function getXsdNsPrefix(
document: vscode.TextDocument,
position: vscode.Position
) {
let initialLineNum = position.line
let lineNum = 0
while (initialLineNum !== 0 && lineNum <= initialLineNum) {
const lineText = document.lineAt(lineNum).text
// returns either empty prefix value or a prefix plus a colon
let text = lineText.match(schemaPrefixRegEx)
if (text != null) {
return text[1]
}
++lineNum
}
//returns the standard prefix plus a colon in the case of missing schema tag
return defaultXsdNsPrefix
}
export function getItemsOnLineCount(triggerText: String) {
let itemsOnLine = 0
let nextPos = 0
let result = 0
if (triggerText.includes('schema')) {
itemsOnLine = 1
return itemsOnLine
}
while (result != -1 && triggerText.includes('<')) {
result = triggerText.indexOf('<', nextPos)
if (result > -1) {
let endPos = triggerText.indexOf('>', nextPos)
if (endPos === -1) {
++itemsOnLine
break
}
let testForCloseTag = triggerText.substring(nextPos, endPos)
if (
!testForCloseTag.includes('</') &&
!testForCloseTag.includes('<!--') &&
!testForCloseTag.includes('-->') &&
!testForCloseTag.includes('<[') &&
!testForCloseTag.includes('<![')
) {
++itemsOnLine
}
result = nextPos
nextPos = endPos + 1
}
}
return itemsOnLine
}
//Determines if the current curser is in XPath and dfdl intellisense should be turned off
export function isInXPath(
document: vscode.TextDocument,
position: vscode.Position
): boolean {
return isXPath(position)
}
export function cursorAfterEquals(
document: vscode.TextDocument,
position: vscode.Position
) {
const triggerText = document.lineAt(position.line).text
const triggerPos = position.character
const textBeforeTrigger = triggerText.substring(0, triggerPos)
let currentPos = -1
if ((currentPos = textBeforeTrigger.lastIndexOf('=')) === -1) {
return false
}
if (triggerPos === currentPos + 1) {
return true
}
return false
}
export function cursorWithinQuotes(
document: vscode.TextDocument,
position: vscode.Position
) {
const quoteChar: string[] = ["'", '"']
let startLine = position.line
for (let i = 0; i < quoteChar.length; ++i) {
let currentText = document.lineAt(startLine).text
if (
currentText.includes('<') &&
!currentText.includes("'") &&
!currentText.includes('"')
) {
return false
}
if (currentText.includes(quoteChar[i])) {
let textBeforeTrigger = currentText.substring(0, position.character)
//let tagStartPos = -1
let quoteStartLine = startLine
let quoteStartPos = -1
let equalStartPos = -1
while (
(equalStartPos = textBeforeTrigger.lastIndexOf('=' + quoteChar[i])) ===
-1
) {
if (textBeforeTrigger.indexOf('<') !== -1) {
break
}
textBeforeTrigger = document.lineAt(--quoteStartLine).text
}
quoteStartPos = equalStartPos + 1
let quoteEndLine = quoteStartLine
let quoteEndPos = -1
if (quoteStartPos > -1) {
while (
quoteEndLine < document.lineCount &&
(quoteEndPos = currentText.indexOf(
quoteChar[i],
quoteStartPos + 1
)) === -1
) {
currentText = document.lineAt(++quoteEndLine).text
}
if (
quoteEndPos > -1 &&
currentText.indexOf('=', quoteStartPos - 1) === quoteStartPos - 1
) {
if (
(position.line > quoteStartLine && position.line < quoteEndLine) ||
(quoteEndLine === quoteStartLine &&
position.character > quoteStartPos &&
position.character <= quoteEndPos) ||
(position.line === quoteStartLine &&
position.character > quoteStartPos &&
position.line < quoteEndLine) ||
(position.line === quoteEndLine &&
position.character <= quoteEndPos &&
position.line > quoteStartLine)
) {
return true
}
}
}
}
}
return false
}
export function cursorWithinBraces(
document: vscode.TextDocument,
position: vscode.Position
) {
let startLine = position.line
let currentText = document.lineAt(startLine).text
let braceStartLine = startLine
let braceStartPos = -1
while (
braceStartLine > 0 &&
(braceStartPos = currentText.indexOf('{')) === -1
) {
currentText = document.lineAt(--braceStartLine).text
}
let braceEndLine = braceStartLine
let braceEndPos = -1
if (braceStartPos > -1) {
while (
braceEndLine < document.lineCount &&
(braceEndPos = currentText.indexOf('}')) === -1
) {
currentText = document.lineAt(++braceEndLine).text
}
if (braceEndPos > -1) {
if (
(position.line > braceStartLine && position.line < braceEndLine) ||
(braceEndLine === braceStartLine &&
position.character > braceStartPos &&
position.character <= braceEndPos) ||
(position.line === braceStartLine &&
position.character > braceStartPos &&
position.line < braceEndLine) ||
(position.line === braceEndLine &&
position.character <= braceEndPos &&
position.line > braceStartLine)
) {
return true
}
}
}
return false
}
export function checkBraceOpen(
document: vscode.TextDocument,
position: vscode.Position
) {
let lineNum = position.line
let triggerText = document.lineAt(lineNum).text
if (triggerText.includes('{')) {
while (!triggerText.includes('}') && lineNum < document.lineCount) {
triggerText = document.lineAt(++lineNum).text
}
if (!triggerText.includes('}')) {
return true
}
}
if (triggerText.includes('}')) {
while (!triggerText.includes('{') && lineNum > 0) {
triggerText = document.lineAt(--lineNum).text
}
if (!triggerText.includes('{')) {
return true
}
}
return false
}
export function createCompletionItem(
e:
| {
item: string
snippetString: string
markdownString: string
}
| {
item: string
snippetString: string
markdownString: undefined
},
preVal: string,
nsPrefix: string
) {
const completionItem = new vscode.CompletionItem(e.item)
const noPreVals = [
'dfdl:choiceBranchKey=',
'dfdl:representation',
'dfdl:choiceDispatchKey=',
'dfdl:simpleType',
'dfdl:element',
'restriction',
]
if (preVal !== '' && !noPreVals.includes(e.item)) {
completionItem.insertText = new vscode.SnippetString(
preVal + e.snippetString
)
} else {
completionItem.insertText = new vscode.SnippetString(e.snippetString)
}
if (e.markdownString) {
completionItem.documentation = new vscode.MarkdownString(e.markdownString)
}
return completionItem
}
export function getCommonItems(
itemsToUse: string[],
preVal: string = '',
additionalItems: string = '',
nsPrefix: string
) {
let compItems: vscode.CompletionItem[] = []
commonCompletion(additionalItems).items.forEach((e) => {
if (itemsToUse.includes(e.item)) {
const completionItem = createCompletionItem(e, preVal, nsPrefix)
compItems.push(completionItem)
}
})
return compItems
}