src/language/providers/closeElement.ts (278 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 { checkMissingCloseTag } from './closeUtils' import { checkBraceOpen, cursorAfterEquals, cursorWithinBraces, cursorWithinQuotes, } from './utils' import { getNsPrefix, insertSnippet, isInXPath, isNotTriggerChar, getItemsOnLineCount, getItemPrefix, } from './utils' export function getCloseElementProvider() { return vscode.languages.registerCompletionItemProvider( 'dfdl', { async provideCompletionItems( document: vscode.TextDocument, position: vscode.Position ) { let triggerChar = '>' if ( checkBraceOpen(document, position) || cursorWithinBraces(document, position) || cursorWithinQuotes(document, position) || cursorAfterEquals(document, position) || isInXPath(document, position) || isNotTriggerChar(document, position, triggerChar) ) { return undefined } let backpos = position.with(position.line, position.character) let backpos3 = position.with(position.line, position.character) if (position.character > 0) { backpos = position.with(position.line, position.character - 1) } if (position.character > 2) { backpos3 = position.with(position.line, position.character - 3) } let nsPrefix = getNsPrefix(document, position) const origPrefix = nsPrefix const nearestTagNotClosed = checkMissingCloseTag( document, position, nsPrefix ) nsPrefix = getItemPrefix(nearestTagNotClosed, origPrefix) const triggerText = document .lineAt(position) .text.substring(0, position.character) let itemsOnLine = getItemsOnLineCount(triggerText) if (nearestTagNotClosed.includes('none')) { return undefined } let range = new vscode.Range(position, position) if ( (triggerText.endsWith('>') && itemsOnLine < 2) || (triggerText.endsWith('>>') && itemsOnLine > 1) || (triggerText.endsWith('.=>') && itemsOnLine === 0) ) { range = new vscode.Range(backpos, position) await vscode.window.activeTextEditor?.edit((editBuilder) => { editBuilder.replace(range, '') }) checkItemsOnLine( document, position, range, itemsOnLine, triggerText, nsPrefix, nearestTagNotClosed, backpos, backpos3 ) } return undefined }, }, '>' // triggered whenever a '>' is typed ) } export function getTDMLCloseElementProvider() { return vscode.languages.registerCompletionItemProvider( 'tdml', { async provideCompletionItems( document: vscode.TextDocument, position: vscode.Position ) { if ( checkBraceOpen(document, position) || cursorWithinBraces(document, position) || cursorWithinQuotes(document, position) || cursorAfterEquals(document, position) || isInXPath(document, position) ) { return undefined } let backpos = position.with(position.line, position.character) let backpos3 = position.with(position.line, position.character) if (position.character > 0) { backpos = position.with(position.line, position.character - 1) } if (position.character > 2) { backpos3 = position.with(position.line, position.character - 3) } let nsPrefix = getNsPrefix(document, position) const origPrefix = nsPrefix const nearestTagNotClosed = checkMissingCloseTag( document, position, nsPrefix ) nsPrefix = getItemPrefix(nearestTagNotClosed, origPrefix) const triggerText = document .lineAt(position) .text.substring(0, position.character) let itemsOnLine = getItemsOnLineCount(triggerText) if (nearestTagNotClosed.includes('none')) { return undefined } let range = new vscode.Range(position, position) if ( (triggerText.endsWith('>') && itemsOnLine < 2) || (triggerText.endsWith('>>') && itemsOnLine > 1) || (triggerText.endsWith('.=>') && itemsOnLine === 0) ) { range = new vscode.Range(backpos, position) await vscode.window.activeTextEditor?.edit((editBuilder) => { editBuilder.replace(range, '') }) checkItemsOnLine( document, position, range, itemsOnLine, triggerText, nsPrefix, nearestTagNotClosed, backpos, backpos3 ) } //return undefined }, }, '>' // triggered whenever a '>' is typed ) } function checkItemsOnLine( document: vscode.TextDocument, position: vscode.Position, range: vscode.Range, itemsOnLine: number, triggerText: string, nsPrefix: string, nearestTagNotClosed: string, backpos: vscode.Position, backpos3: vscode.Position ) { if ( itemsOnLine == 0 && !triggerText.includes('</') && !triggerText.includes('/>') ) { if (triggerText.trim() === '>') { insertSnippet('</' + nsPrefix + nearestTagNotClosed + '>', backpos) } else { switch (nearestTagNotClosed) { case 'schema': if (triggerText.endsWith('>>')) { insertSnippet( '\n\t$0\n</' + nsPrefix + nearestTagNotClosed + '>', backpos ) } else { insertSnippet( '>\n\t$0\n</' + nsPrefix + nearestTagNotClosed + '>', backpos ) } break default: if (triggerText.endsWith('>>')) { insertSnippet( '</' + nsPrefix + nearestTagNotClosed + '>$0', backpos ) } else { insertSnippet( '>$1</' + nsPrefix + nearestTagNotClosed + '>', backpos ) } break } } } if ( itemsOnLine === 1 && !triggerText.includes('</') && !triggerText.includes('/>') ) { checkNearestTagNotClosed( document, position, range, nearestTagNotClosed, backpos, nsPrefix ) } if (itemsOnLine > 1) { checkTriggerText(triggerText, nsPrefix, backpos, nearestTagNotClosed) } } function checkNearestTagNotClosed( document: vscode.TextDocument, position: vscode.Position, range: vscode.Range, nearestTagNotClosed: string, backpos: vscode.Position, nsPrefix: string ) { const triggerText = document.lineAt(position.line).text switch (nearestTagNotClosed) { case 'defineVariable': case 'setVariable': insertSnippet('>\n</' + nsPrefix + nearestTagNotClosed + '>$0', backpos) break case 'assert': case 'discriminator': if (triggerText.endsWith('>')) { insertSnippet('$1</' + nsPrefix + nearestTagNotClosed + '>', backpos) } else { insertSnippet('>$1</' + nsPrefix + nearestTagNotClosed + '>$0', backpos) } break default: if (triggerText.trim() === '') { insertSnippet('</' + nsPrefix + nearestTagNotClosed + '>', backpos) } else { insertSnippet( '>\n\t$0\n</' + nsPrefix + nearestTagNotClosed + '>', backpos ) } break } } function checkTriggerText( triggerText: string, nsPrefix: string, backpos: vscode.Position, nearestTagNotClosed: string ) { if (triggerText.includes('<' + nsPrefix + nearestTagNotClosed)) { let tagPos = triggerText.lastIndexOf('<' + nsPrefix + nearestTagNotClosed) let tagEndPos = triggerText.indexOf('>', tagPos) if ( tagPos != -1 && !triggerText.substring(tagEndPos - 1, 2).includes('/>') && !triggerText .substring(tagEndPos) .includes('</' + nsPrefix + nearestTagNotClosed) ) { if (triggerText.endsWith('>>')) { insertSnippet('</' + nsPrefix + nearestTagNotClosed + '>$0', backpos) } else { insertSnippet('></' + nsPrefix + nearestTagNotClosed + '>$0', backpos) } } } }