You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@daffodil.apache.org by sh...@apache.org on 2023/04/12 13:00:56 UTC

[daffodil-vscode] branch main updated: Provides the ability to re-select values of attributes items - add logic to mitigate missing dfdl prefixes - tweak logic to determine if cursor is between quotes - Fixes incorrect velues triggered by nested elements - fixes the resulting choice when intellisense is trigger between two closing tags on a multi tag line - fix wrong suggestions at end of schema open tag - fix incorrect suggestion at beginning and end of a multi tag line - fix broken attribute suggestions from inside multiline [...]

This is an automated email from the ASF dual-hosted git repository.

shanedell 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 57513b4  Provides the ability to re-select values of attributes items - add logic to mitigate missing dfdl prefixes - tweak logic to determine if cursor is between quotes - Fixes incorrect velues triggered by nested elements - fixes the resulting choice when intellisense is trigger between two closing tags on a multi tag line - fix wrong suggestions at end of schema open tag - fix incorrect suggestion at beginning and end of a multi tag line - fix broken attribute suggestions fro [...]
57513b4 is described below

commit 57513b49e8b7ae10e3d4c547495a3f0463bf42d2
Author: rt320 <98...@users.noreply.github.com>
AuthorDate: Wed Apr 5 11:44:05 2023 -0400

    Provides the ability to re-select values of attributes items
    - add logic to mitigate missing dfdl prefixes
    - tweak logic to determine if cursor is between quotes
    - Fixes incorrect velues triggered by nested elements
    - fixes the resulting choice when intellisense is trigger between
    two closing tags on a multi tag line
    - fix wrong suggestions at end of schema open tag
    - fix incorrect suggestion at beginning and end of a multi tag line
    - fix broken attribute suggestions from inside multiline tag
    - fixed cursor between alert open and close tags was returning
    results for appinfo
    - Change logic to for assert and discriminator auto complete
    add white space
    - Remove prettier-ignore directive from attributeValueItems.ts
    Closes #573
    Closes #577
    Closes #578
---
 src/language/dfdl.ts                               |   2 +
 src/language/providers/attributeCompletion.ts      |  11 +-
 src/language/providers/attributeValueCompletion.ts | 145 ++++++++++
 src/language/providers/closeElement.ts             |  21 +-
 src/language/providers/closeElementSlash.ts        |   9 +-
 src/language/providers/closeUtils.ts               |  44 +++-
 src/language/providers/elementCompletion.ts        |  45 +++-
 .../providers/intellisense/attributeItems.ts       |   2 +-
 .../providers/intellisense/attributeValueItems.ts  | 292 +++++++++++++++++++++
 .../providers/intellisense/elementItems.ts         |  19 +-
 src/language/providers/utils.ts                    | 162 +++++++++++-
 src/tests/suite/language/items.test.ts             |   3 +
 12 files changed, 723 insertions(+), 32 deletions(-)

diff --git a/src/language/dfdl.ts b/src/language/dfdl.ts
index a60dd6b..6628416 100644
--- a/src/language/dfdl.ts
+++ b/src/language/dfdl.ts
@@ -20,6 +20,7 @@ import * as fs from 'fs'
 import { getElementCompletionProvider } from './providers/elementCompletion'
 import { getAttributeCompletionProvider } from './providers/attributeCompletion'
 import { getCloseElementProvider } from './providers/closeElement'
+import { getAttributeValueCompletionProvider } from './providers/attributeValueCompletion'
 import { getCloseElementSlashProvider } from './providers/closeElementSlash'
 
 export function activate(context: vscode.ExtensionContext) {
@@ -34,6 +35,7 @@ export function activate(context: vscode.ExtensionContext) {
   context.subscriptions.push(
     getElementCompletionProvider(dfdlFormat),
     getAttributeCompletionProvider(),
+    getAttributeValueCompletionProvider(),
     getCloseElementProvider(),
     getCloseElementSlashProvider()
   )
diff --git a/src/language/providers/attributeCompletion.ts b/src/language/providers/attributeCompletion.ts
index d2aed4a..d3ff53b 100644
--- a/src/language/providers/attributeCompletion.ts
+++ b/src/language/providers/attributeCompletion.ts
@@ -25,8 +25,10 @@ import {
   getCommonItems,
   getXsdNsPrefix,
   getItemsOnLineCount,
+  cursorWithinQuotes,
   cursorWithinBraces,
   dfdlDefaultPrefix,
+  cursorAfterEquals,
 } from './utils'
 
 import { attributeCompletion } from './intellisense/attributeItems'
@@ -76,6 +78,8 @@ export function getAttributeCompletionProvider() {
         if (
           checkBraceOpen(document, position) ||
           cursorWithinBraces(document, position) ||
+          cursorWithinQuotes(document, position) ||
+          cursorAfterEquals(document, position) ||
           nearestOpenItem.includes('none')
         ) {
           return undefined
@@ -101,7 +105,10 @@ export function getAttributeCompletionProvider() {
   )
 }
 
-function getDefinedTypes(document: vscode.TextDocument, nsPrefix: string) {
+export function getDefinedTypes(
+  document: vscode.TextDocument,
+  nsPrefix: string
+) {
   let additionalTypes = ''
   let lineNum = 0
   const lineCount = document.lineCount
@@ -229,7 +236,7 @@ function checkNearestOpenItem(
         ''
       )
     case 'discriminator':
-      return getCompletionItems(['message'], '', '', nsPrefix, '')
+      return getCompletionItems(['test', 'message'], '', '', nsPrefix, '')
     case 'format':
       return getCompletionItems(
         [
diff --git a/src/language/providers/attributeValueCompletion.ts b/src/language/providers/attributeValueCompletion.ts
new file mode 100644
index 0000000..7f0e3ff
--- /dev/null
+++ b/src/language/providers/attributeValueCompletion.ts
@@ -0,0 +1,145 @@
+/*
+ * 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, getXsdNsPrefix } 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 nsPrefix = getXsdNsPrefix(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 newline 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
+        ) {
+          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]
+}
diff --git a/src/language/providers/closeElement.ts b/src/language/providers/closeElement.ts
index 4450fe5..399c942 100644
--- a/src/language/providers/closeElement.ts
+++ b/src/language/providers/closeElement.ts
@@ -17,7 +17,12 @@
 
 import * as vscode from 'vscode'
 import { checkMissingCloseTag } from './closeUtils'
-import { checkBraceOpen, cursorWithinBraces } from './utils'
+import {
+  checkBraceOpen,
+  cursorAfterEquals,
+  cursorWithinBraces,
+  cursorWithinQuotes,
+} from './utils'
 import {
   getXsdNsPrefix,
   insertSnippet,
@@ -35,19 +40,24 @@ export function getCloseElementProvider() {
       ) {
         if (
           checkBraceOpen(document, position) ||
-          cursorWithinBraces(document, position)
+          cursorWithinBraces(document, position) ||
+          cursorWithinQuotes(document, position) ||
+          cursorAfterEquals(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 = getXsdNsPrefix(document, position)
         const origPrefix = nsPrefix
 
@@ -68,6 +78,7 @@ export function getCloseElementProvider() {
         }
 
         let range = new vscode.Range(position, position)
+
         if (
           (triggerText.endsWith('>') && itemsOnLine < 2) ||
           (triggerText.endsWith('>>') && itemsOnLine > 1) ||
@@ -169,6 +180,7 @@ function checkNearestTagNotClosed(
   nsPrefix: string
 ) {
   const triggerText = document.lineAt(position.line).text
+
   switch (nearestTagNotClosed) {
     case 'defineVariable':
     case 'setVariable':
@@ -177,9 +189,9 @@ function checkNearestTagNotClosed(
     case 'assert':
     case 'discriminator':
       if (triggerText.endsWith('>')) {
-        insertSnippet('</' + nsPrefix + nearestTagNotClosed + '>', backpos)
+        insertSnippet('$1</' + nsPrefix + nearestTagNotClosed + '>', backpos)
       } else {
-        insertSnippet('></' + nsPrefix + nearestTagNotClosed + '>$0', backpos)
+        insertSnippet('>$1</' + nsPrefix + nearestTagNotClosed + '>$0', backpos)
       }
       break
     default:
@@ -204,6 +216,7 @@ function checkTriggerText(
   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('/>') &&
diff --git a/src/language/providers/closeElementSlash.ts b/src/language/providers/closeElementSlash.ts
index 49d47bd..3137e30 100644
--- a/src/language/providers/closeElementSlash.ts
+++ b/src/language/providers/closeElementSlash.ts
@@ -24,6 +24,8 @@ import {
   getItemPrefix,
   getItemsOnLineCount,
   cursorWithinBraces,
+  cursorWithinQuotes,
+  cursorAfterEquals,
 } from './utils'
 
 export function getCloseElementSlashProvider() {
@@ -48,7 +50,9 @@ export function getCloseElementSlashProvider() {
 
         if (
           checkBraceOpen(document, position) ||
-          cursorWithinBraces(document, position)
+          cursorWithinBraces(document, position) ||
+          cursorWithinQuotes(document, position) ||
+          cursorAfterEquals(document, position)
         ) {
           return undefined
         }
@@ -89,8 +93,10 @@ function checkItemsOnLine(
   triggerText: string
 ) {
   nsPrefix = getItemPrefix(nearestTagNotClosed, nsPrefix)
+
   if (itemsOnLine == 1 || itemsOnLine == 0) {
     insertSnippet('/>$0', backpos)
+
     if (
       nearestTagNotClosed.includes('defineVariable') ||
       nearestTagNotClosed.includes('setVariable')
@@ -111,6 +117,7 @@ function checkItemsOnLine(
     ) {
       let tagPos = triggerText.lastIndexOf('<' + nsPrefix + nearestTagNotClosed)
       let tagEndPos = triggerText.indexOf('>', tagPos)
+
       if (
         tagPos != -1 &&
         !triggerText.substring(tagEndPos - 1, 2).includes('/>') &&
diff --git a/src/language/providers/closeUtils.ts b/src/language/providers/closeUtils.ts
index 12d1378..1b931cf 100644
--- a/src/language/providers/closeUtils.ts
+++ b/src/language/providers/closeUtils.ts
@@ -78,6 +78,7 @@ export function cursorInsideCloseTag(
   const triggerPos = position.character
   const closeTagStart = triggerText.lastIndexOf('</')
   const closeTagEnd = triggerText.lastIndexOf('>')
+
   if (
     triggerPos > closeTagStart &&
     triggerPos <= closeTagEnd &&
@@ -126,10 +127,13 @@ export function getCloseTag(
       tagOpen = tagClose + 1
     }
   } else {
+    let nestedTagCount = 0
     let endPos = triggerText.indexOf('>', startPos)
+
     if (triggerText.includes('?xml version')) {
       return [tag, 0, 0]
     }
+
     if (
       (triggerText.includes('</') || triggerText.includes('/>')) &&
       triggerText.includes(tag) &&
@@ -162,9 +166,30 @@ export function getCloseTag(
           //skipping to closing tag
           while (!currentText.includes('</' + nsPrefix + tag)) {
             currentText = document.lineAt(++lineNum).text
+
+            //If currentText is multi tag line skip to next line
             if (getItemsOnLineCount(currentText) > 1) {
               currentText = document.lineAt(++lineNum).text
             }
+
+            if (currentText.includes('<' + nsPrefix + tag)) {
+              ++nestedTagCount
+              while (!currentText.includes('>')) {
+                currentText = document.lineAt(++lineNum).text
+              }
+              if (currentText.includes('/>')) {
+                --nestedTagCount
+              }
+            }
+
+            //if currentText is a closing tag
+            if (
+              currentText.includes('</' + nsPrefix + tag) &&
+              nestedTagCount > 0
+            ) {
+              --nestedTagCount
+              currentText = ''
+            }
           }
         }
 
@@ -174,10 +199,12 @@ export function getCloseTag(
           !currentText.includes('>')
         ) {
           isMultiLineTag = true
+
           //skip to the end tag symbol
           while (!currentText.includes('>')) {
             currentText = document.lineAt(++lineNum).text
           }
+
           //if the tag isn't self closing, skip to the closing tag
           if (!currentText.includes('/>')) {
             while (!currentText.includes('</' + nsPrefix + tag)) {
@@ -187,12 +214,14 @@ export function getCloseTag(
         }
 
         if (
-          currentText.includes('</' + nsPrefix + tag) ||
+          (currentText.includes('</' + nsPrefix + tag) &&
+            nestedTagCount === 0) ||
           (currentText.includes('/>') && isMultiLineTag)
         ) {
           if (isMultiLineTag) {
             startPos = triggerPos
           }
+
           //if the cursor is after the closing tag
           if (
             lineNum == triggerLine &&
@@ -200,14 +229,13 @@ export function getCloseTag(
           ) {
             return ['none', lineNum, startPos]
           }
+
           return [tag, lineNum, startPos]
         }
       }
-
       ++lineNum
     }
   }
-
   return ['none', 0, 0]
 }
 
@@ -257,7 +285,7 @@ export function getItemsForLineGT1(
     return items[i]
   }
 
-  return undefined
+  return 'none'
 }
 
 export function getItemsForLineLT2(
@@ -278,6 +306,7 @@ export function getItemsForLineLT2(
   let closeTagArray: number[] = []
 
   nsPrefix = getItemPrefix(items[i], nsPrefix)
+
   while (
     currentText.indexOf('<' + nsPrefix + items[i]) === -1 &&
     currentLine > -1
@@ -296,6 +325,7 @@ export function getItemsForLineLT2(
   if (currentText.indexOf('<' + nsPrefix + items[i]) > -1) {
     while (lineBefore > -1) {
       currentText = document.lineAt(lineBefore).text
+
       if (getItemsOnLineCount(currentText) < 2) {
         if (currentText.indexOf('<' + nsPrefix + items[i]) > -1) {
           openTagArray.push(lineBefore)
@@ -310,7 +340,11 @@ export function getItemsForLineLT2(
           }
 
           //if selfclosing remove from the array
-          if (testText.indexOf('/>') > -1 || testText.includes('xml version')) {
+          if (
+            testText.indexOf('/>') > -1 ||
+            testText.includes('xml version') ||
+            currentText.indexOf('</' + nsPrefix + items[i]) > -1
+          ) {
             openTagArray.splice(openTagArray.length - 1, 1)
           }
         }
diff --git a/src/language/providers/elementCompletion.ts b/src/language/providers/elementCompletion.ts
index fb28848..f9f836a 100644
--- a/src/language/providers/elementCompletion.ts
+++ b/src/language/providers/elementCompletion.ts
@@ -26,6 +26,8 @@ import {
   nearestTag,
   getItemsOnLineCount,
   cursorWithinBraces,
+  cursorWithinQuotes,
+  cursorAfterEquals,
 } from './utils'
 import { elementCompletion } from './intellisense/elementItems'
 
@@ -39,7 +41,9 @@ export function getElementCompletionProvider(dfdlFormatString: string) {
     ) {
       if (
         checkBraceOpen(document, position) ||
-        cursorWithinBraces(document, position)
+        cursorWithinBraces(document, position) ||
+        cursorWithinQuotes(document, position) ||
+        cursorAfterEquals(document, position)
       ) {
         return undefined
       }
@@ -49,9 +53,20 @@ export function getElementCompletionProvider(dfdlFormatString: string) {
       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 (checkMissingCloseTag(document, position, nsPrefix) !== 'none') {
+        if (missingCloseTag !== 'none') {
+          return undefined
+        }
+        if (
+          missingCloseTag === 'none' &&
+          itemsOnLine > 1 &&
+          (triggerPos === lastCloseSymbol + 1 || triggerPos === firstOpenSymbol)
+        ) {
           return undefined
         }
 
@@ -215,6 +230,10 @@ function checkTagNearestOpen(
         '',
         nsPrefix
       )
+    case 'assert':
+      return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
+    case 'discriminator':
+      return getElementCompletionItems(['CDATA', '{}'], '', '', nsPrefix)
     case 'defineFormat':
       return getElementCompletionItems(['format'], '', '', nsPrefix)
     case 'schema':
@@ -271,12 +290,23 @@ export function getTagNearestTrigger(
     )
 
     if (itemsOnLine > 1) {
-      let afterTrigger =
-        triggerText.substring(triggerPos).indexOf('<') + triggerPos
-      let beforeTrigger = triggerText.substring(0, triggerPos).lastIndexOf('>')
-      if (triggerPos === afterTrigger && triggerPos === beforeTrigger + 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
+        return tagNearestTrigger
+      }
     }
 
     startLine = foundLine
@@ -295,6 +325,7 @@ export function getTagNearestTrigger(
         tagNearestTrigger = foundTag
         return tagNearestTrigger
       }
+
       if (endTag === 'none') {
         startLine = foundLine - 1
       } else {
diff --git a/src/language/providers/intellisense/attributeItems.ts b/src/language/providers/intellisense/attributeItems.ts
index 6d5f20e..27ee6d1 100644
--- a/src/language/providers/intellisense/attributeItems.ts
+++ b/src/language/providers/intellisense/attributeItems.ts
@@ -236,7 +236,7 @@ export const attributeCompletion = (additionalItems, nsPrefix: string, dfdlPrefi
       },
       {
         item: 'dfdl:leadingSkip',
-        snippetString: dfdlPrefix + 'trailingSkip="0$1"$0',
+        snippetString: dfdlPrefix + 'leadingSkip="0$1"$0',
         markdownString: 'A non-negative number of bytes or bits to skip before alignment is applied',
       },
       {
diff --git a/src/language/providers/intellisense/attributeValueItems.ts b/src/language/providers/intellisense/attributeValueItems.ts
new file mode 100644
index 0000000..9b28a18
--- /dev/null
+++ b/src/language/providers/intellisense/attributeValueItems.ts
@@ -0,0 +1,292 @@
+/*
+ * 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 { insertSnippet } from '../utils'
+
+export const noChoiceAttributes = [
+  'name',
+  'ref',
+  'occursCount',
+  'length',
+  'prefixLengthType',
+  'nilValue',
+  'lengthPattern',
+  'inputValueCalc',
+  'outputValueCalc',
+  'hiddenGroupRef',
+  'choiceBranchKey',
+  'textNumberRoundingIncrement',
+  'separator',
+  'terminator',
+  'choiceLength',
+  'fillByte',
+  'initiator',
+  'choiceDispatchKey',
+  'escapeSchemeRef',
+  'test',
+  'testPattern',
+  'message',
+]
+
+export function attributeValues(
+  attributeName: string,
+  startPos: vscode.Position,
+  additionalTypes: string
+) {
+  switch (attributeName) {
+    case 'minOccurs':
+      insertSnippet('"${1|0,1|}"$0', startPos)
+      break
+    case 'maxOccurs':
+      insertSnippet('"${1|0,1,unbounded|}"$0', startPos)
+      break
+    case 'occursCount':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'byteOrder':
+      insertSnippet('"${1|bigEndian,littleEndian|}"$0', startPos)
+      break
+    case 'bitOrder':
+      insertSnippet(
+        '"${1|mostSignificantBitFirst,leastSignificantBitFirst|}"$0',
+        startPos
+      )
+      break
+    case 'occursCountKind':
+      insertSnippet(
+        '"${1|expression,fixed,implicit,parsed,stopValue|}"$0',
+        startPos
+      )
+      break
+    case 'length':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'lengthKind':
+      insertSnippet(
+        '"${1|delimited,fixed,explicit,implicit,prefixed,patternendOfParent|}"$0',
+        startPos
+      )
+      break
+    case 'prefixIncludesPrefixLength':
+      insertSnippet('"${1|yes,no|}"$0', startPos)
+      break
+    case 'prefixLengthType':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'utf16Width':
+      insertSnippet('"${1|fixed,variable|}"$0', startPos)
+      break
+    case 'encoding':
+      insertSnippet(
+        '"${1|US-ASCII,ASCII,UTF-8,UTF-16,UTF-16BE,UTF-16LE,ISO-8859-1|}"$0',
+        startPos
+      )
+      break
+    case 'encodingErrorPolicy':
+      insertSnippet('"${1|error,replace|}"$0', startPos)
+      break
+    case 'nilKind':
+      insertSnippet(
+        '"${1|literalCharacter,literalValue,logicalValue|}"$0',
+        startPos
+      )
+      break
+    case 'nilValue':
+      insertSnippet('nilValue="$1"$0', startPos)
+      break
+    case 'nilValueDelimiterPolicy':
+      insertSnippet('"${1|initiator,terminator,both,none|}"$0', startPos)
+      break
+    case 'alignment':
+      insertSnippet('"${1|1,2,implicit|}"$0', startPos)
+      break
+    case 'lengthUnits':
+      insertSnippet('"${1|bits,bytes,characters|}"$0', startPos)
+      break
+    case 'lengthPattern':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'inputValueCalc':
+      insertSnippet('"{$1}"$0', startPos)
+      break
+    case 'outputValueCalc':
+      insertSnippet('"{$1}"$0', startPos)
+      break
+    case 'alignmentUnits':
+      insertSnippet('"${1|bits,bytes|}"$0', startPos)
+      break
+    case 'outputNewLine':
+      insertSnippet('"${1|%CR;,%LF;,%CR;%LF;,%NEL;,%LS;|}"$0', startPos)
+      break
+    case 'choiceBranchKey':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'representation':
+      insertSnippet('"${1|binary,text|}"$0', startPos)
+      break
+    case 'textStringJustification':
+      insertSnippet('"${1|left,right,center|}"$0', startPos)
+      break
+    case 'textStandardZeroRep':
+      insertSnippet('"0"$0', startPos)
+      break
+    case 'textStandardInfinityRep':
+      insertSnippet('"Inf"$0', startPos)
+      break
+    case 'textStandardExponentRep':
+      insertSnippet('"E"$0', startPos)
+      break
+    case 'textStandardNaNRep':
+      insertSnippet('"NaN"$0', startPos)
+      break
+    case 'textNumberPattern':
+      insertSnippet('"#,##0.###;-#,##0.###"$0', startPos)
+      break
+    case 'textNumberRep':
+      insertSnippet('"${1|standard,zoned|}"$0', startPos)
+      break
+    case 'textNumberRoundingMode':
+      insertSnippet(
+        '"${1|roundCeiling,roundFloor,roundDown,roundUp,roundHalfEven,roundHalfDown,roundHalfUp,roundUnnecessary|}"$0',
+        startPos
+      )
+      break
+    case 'textNumberRoundingIncrement':
+      insertSnippet('"0"$0', startPos)
+      break
+    case 'textNumberRounding':
+      insertSnippet('"${1|explicit,pattern|}"$0', startPos)
+      break
+    case 'textNumberCheckPolicy':
+      insertSnippet('"${1|lax,strict|}"$0', startPos)
+      break
+    case 'textOutputMinLength':
+      insertSnippet('"0"$0', startPos)
+      break
+    case 'textStandardGroupingSeparator':
+      insertSnippet('","$0', startPos)
+      break
+    case 'textPadKind':
+      insertSnippet('"${1|none,padChar|}"$0', startPos)
+      break
+    case 'textStandardBase':
+      insertSnippet('"${1|2,8,10,16|}"$0', startPos)
+      break
+    case 'textTrimKind':
+      insertSnippet('"${1|none,padChar|}"$0', startPos)
+      break
+    case 'leadingSkip':
+      insertSnippet('"0$1"$0', startPos)
+      break
+    case 'trailingSkip':
+      insertSnippet('"0$1"$0', startPos)
+      break
+    case 'truncateSpecifiedLengthString':
+      insertSnippet('"${1|no,yes|}"$0', startPos)
+      break
+    case 'sequenceKind':
+      insertSnippet('"${1|ordered,unordered|}"$0', startPos)
+      break
+    case 'separator':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'separatorPosition':
+      insertSnippet('"${1|infix,postfix,prefix|}"$0', startPos)
+      break
+    case 'separatorSuppressionPolicy':
+      insertSnippet(
+        '"${1|anyEmpty,never,trailingEmpty,trailingEmptyStrict|}"$0',
+        startPos
+      )
+      break
+    case 'terminator':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'textBidi':
+      insertSnippet('"${1|no,yes|}"$0', startPos)
+      break
+    case 'hiddenGroupRef':
+      insertSnippet('"$1"\n$0', startPos)
+      break
+    case 'choiceLengthKind':
+      insertSnippet('"${1|explicit,implicit|}"$0', startPos)
+      break
+    case 'choiceLength':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'fillByte':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'ignoreCase':
+      insertSnippet('"${1|no,yes|}"$0', startPos)
+      break
+    case 'initiatedContent':
+      insertSnippet('"${1|yes,no|}"$0', startPos)
+      break
+    case 'initiator':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'choiceDispatchKey':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'binaryNumberRep':
+      insertSnippet('"${1|binary,packed,bcd,ibm4690Packed|}"$0', startPos)
+      break
+    case 'floating':
+      insertSnippet('"${1|no,yes|}"$0', startPos)
+      break
+    case 'binaryFloatRep':
+      insertSnippet('"${1|ieee,ibm390Hex|}"$0', startPos)
+      break
+    case 'calendarPatternKind':
+      insertSnippet('"${1|explicit,implicit|}"$0', startPos)
+      break
+    case 'documentFinalTerminatorCanBeMissing':
+      insertSnippet('"${1|yes,no|}"$0', startPos)
+      break
+    case 'emptyValueDelimiterPolicy':
+      insertSnippet('"${1|initiator,terminator,both,none|}"$0', startPos)
+      break
+    case 'escapeSchemeRef':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'testKind':
+      insertSnippet('"${1|expression,pattern|}"$0', startPos)
+      break
+    case 'test':
+      insertSnippet('"{$1}"$0', startPos)
+      break
+    case 'testPattern':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'message':
+      insertSnippet('"$1"$0', startPos)
+      break
+    case 'failureType':
+      insertSnippet('"${1|processingError,recoverableError|}"$0', startPos)
+      break
+    case 'type':
+      insertSnippet(
+        '"${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' +
+          additionalTypes +
+          '|}"$0',
+        startPos
+      )
+      break
+  }
+}
diff --git a/src/language/providers/intellisense/elementItems.ts b/src/language/providers/intellisense/elementItems.ts
index a79f43f..ca82eb6 100644
--- a/src/language/providers/intellisense/elementItems.ts
+++ b/src/language/providers/intellisense/elementItems.ts
@@ -53,12 +53,12 @@ export const elementCompletion = (definedVariables, dfdlFormatString, nsPrefix)
       },
       {
         item: 'dfdl:assert',
-        snippetString: '<dfdl:assert>$1\n</dfdl:assert>$0',
+        snippetString: '<dfdl:assert $0',
         markdownString: 'Used to assert truths about a DFDL model',
       },
       {
         item: 'dfdl:discriminator',
-        snippetString: '<dfdl:discriminator test="{$1}"/>$0',
+        snippetString: '<dfdl:discriminator $0',
         markdownString: 'Used during parsing to resolve points or uncertainity, remove ambiguity during speculative parsing, improve diagnostic behavior',
       },
       {
@@ -160,6 +160,21 @@ export const elementCompletion = (definedVariables, dfdlFormatString, nsPrefix)
         snippetString: '<' + nsPrefix + 'maxExclusive value="$1"/>$0',
         markdownString: 'Used to check the validity of an element'
       },
+      {
+        item: '<[CDATA[]]>',
+        snippetString: '<[CDATA[$1]]>$0',
+        markdownString: ''
+      },
+      {
+        item: '<![CDATA[]]>',
+        snippetString: '<![CDATA[$1]]>$0',
+        markdownString: ''
+      },
+      {
+        item: '{}',
+        snippetString: '{$1}$0',
+        markdownString: ''
+      },
     ],
   }
 }
diff --git a/src/language/providers/utils.ts b/src/language/providers/utils.ts
index 6c711e4..b50d4a4 100644
--- a/src/language/providers/utils.ts
+++ b/src/language/providers/utils.ts
@@ -69,10 +69,12 @@ export function lineCount(
   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) &&
@@ -92,6 +94,7 @@ export function nearestOpen(
     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]
@@ -114,6 +117,7 @@ export function nearestTag(
   const itemsOnLine = getItemsOnLineCount(document.lineAt(lineNum).text)
   let tagPos = triggerText.indexOf('<')
   let endPos = triggerText.lastIndexOf('>')
+
   if (
     itemsOnLine > 1 &&
     startPos !== tagPos &&
@@ -122,15 +126,18 @@ export function nearestTag(
   ) {
     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]
           }
@@ -145,15 +152,28 @@ export function nearestTag(
     ) {
       --lineNum
     }
+
     while (lineNum > -1 && lineNum < document.lineCount) {
       let currentText = document.lineAt(lineNum).text
+
       if (getItemsOnLineCount(currentText) < 2) {
-        if (!currentText.includes('</') && !currentText.includes('/>')) {
+        if (!currentText.includes('/>')) {
           for (let i = 0; i < items.length; ++i) {
             nsPrefix = getItemPrefix(items[i], origPrefix)
+
             if (
-              currentText.includes('<' + nsPrefix + items[i]) ||
-              (lineNum === 0 && currentText.includes(items[i]))
+              !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]
             }
@@ -180,17 +200,21 @@ export function checkTagOpen(
   let isMultiLineTag = false
   let origTriggerText = triggerText
   let origTriggerLine = triggerLine
-  while (itemsOnLine < 2 && triggerText.indexOf('<') === -1) {
+  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
   }
-  const triggerPos = position.character
-  const textBeforeTrigger = triggerText.substring(0, triggerPos)
+
   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 &&
@@ -206,20 +230,25 @@ export function checkTagOpen(
     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) ||
+      (origTriggerLine == position.line &&
+        triggerPos <= tagEndPos &&
+        triggerPos > tagPos) ||
       position.line < origTriggerLine
     ) {
       return true
     }
   }
+
   if (!isMultiLineTag || tagPos === -1) {
     return false
   }
@@ -238,6 +267,7 @@ export function checkTagOpen(
 
 export function getItemPrefix(item: string, nsPrefix: string) {
   let itemPrefix = nsPrefix
+
   if (
     item === 'assert' ||
     item === 'discriminator' ||
@@ -247,9 +277,11 @@ export function getItemPrefix(item: string, nsPrefix: string) {
   ) {
     itemPrefix = 'dfdl:'
   }
+
   if (item === 'xml version') {
     itemPrefix = '?'
   }
+
   if (item === 'dfdl:element' || item === 'dfdl:simpleType') {
     itemPrefix = ''
   }
@@ -269,7 +301,8 @@ export function checkMultiLineTag(
     return false
   }
   let currentLine = position.line
-  let currentText = document.lineAt(currentLine).text
+  const origText = document.lineAt(currentLine).text
+  let currentText = origText
 
   //the current line doesn't have the self close symbol
   if (!currentText.endsWith('/>')) {
@@ -277,19 +310,24 @@ export function checkMultiLineTag(
       --currentLine
       currentText = document.lineAt(currentLine).text
     }
+
     if (
       currentText.indexOf('<' + nsPrefix + tag) !== -1 &&
       currentText.indexOf('>') === -1 &&
-      (currentText.indexOf('<' + nsPrefix + tag) < position.character ||
-        currentLine < position.line)
+      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) ||
@@ -308,10 +346,12 @@ export function getXsdNsPrefix(
 ) {
   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]
     }
@@ -325,10 +365,12 @@ 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) {
@@ -338,6 +380,7 @@ export function getItemsOnLineCount(triggerText: String) {
         break
       }
       let testForCloseTag = triggerText.substring(nextPos, endPos)
+
       if (
         !testForCloseTag.includes('</') &&
         !testForCloseTag.includes('<!--') &&
@@ -354,6 +397,99 @@ export function getItemsOnLineCount(triggerText: String) {
   return itemsOnLine
 }
 
+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
@@ -362,6 +498,7 @@ export function cursorWithinBraces(
   let currentText = document.lineAt(startLine).text
   let braceStartLine = startLine
   let braceStartPos = -1
+
   while (
     braceStartLine > 0 &&
     (braceStartPos = currentText.indexOf('{')) === -1
@@ -370,6 +507,7 @@ export function cursorWithinBraces(
   }
   let braceEndLine = braceStartLine
   let braceEndPos = -1
+
   if (braceStartPos > -1) {
     while (
       braceEndLine < document.lineCount &&
@@ -377,6 +515,7 @@ export function cursorWithinBraces(
     ) {
       currentText = document.lineAt(++braceEndLine).text
     }
+
     if (braceEndPos > -1) {
       if (
         (position.line > braceStartLine && position.line < braceEndLine) ||
@@ -408,14 +547,17 @@ export function checkBraceOpen(
     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
     }
diff --git a/src/tests/suite/language/items.test.ts b/src/tests/suite/language/items.test.ts
index 01b6f7b..02c2926 100644
--- a/src/tests/suite/language/items.test.ts
+++ b/src/tests/suite/language/items.test.ts
@@ -51,6 +51,9 @@ suite('Items Test Suite', () => {
     'minExclusive',
     'maxInclusive',
     'maxExclusive',
+    '<[CDATA[]]>',
+    '<![CDATA[]]>',
+    '{}',
   ]
   const expectedAttributeItems = [
     'name',