xml-hint.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // CodeMirror, copyright (c) by Marijn Haverbeke and others
  2. // Distributed under an MIT license: https://codemirror.net/LICENSE
  3. ;(function (mod) {
  4. if (typeof exports == 'object' && typeof module == 'object')
  5. // CommonJS
  6. mod(require('../../lib/codemirror'))
  7. else if (typeof define == 'function' && define.amd)
  8. // AMD
  9. define(['../../lib/codemirror'], mod)
  10. // Plain browser env
  11. else mod(CodeMirror)
  12. })(function (CodeMirror) {
  13. 'use strict'
  14. var Pos = CodeMirror.Pos
  15. function matches(hint, typed, matchInMiddle) {
  16. if (matchInMiddle) return hint.indexOf(typed) >= 0
  17. else return hint.lastIndexOf(typed, 0) == 0
  18. }
  19. function getHints(cm, options) {
  20. var tags = options && options.schemaInfo
  21. var quote = (options && options.quoteChar) || '"'
  22. var matchInMiddle = options && options.matchInMiddle
  23. if (!tags) return
  24. var cur = cm.getCursor(),
  25. token = cm.getTokenAt(cur)
  26. if (token.end > cur.ch) {
  27. token.end = cur.ch
  28. token.string = token.string.slice(0, cur.ch - token.start)
  29. }
  30. var inner = CodeMirror.innerMode(cm.getMode(), token.state)
  31. if (!inner.mode.xmlCurrentTag) return
  32. var result = [],
  33. replaceToken = false,
  34. prefix
  35. var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string)
  36. var tagName = tag && /^\w/.test(token.string),
  37. tagStart
  38. if (tagName) {
  39. var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start)
  40. var tagType = /<\/$/.test(before) ? 'close' : /<$/.test(before) ? 'open' : null
  41. if (tagType) tagStart = token.start - (tagType == 'close' ? 2 : 1)
  42. } else if (tag && token.string == '<') {
  43. tagType = 'open'
  44. } else if (tag && token.string == '</') {
  45. tagType = 'close'
  46. }
  47. var tagInfo = inner.mode.xmlCurrentTag(inner.state)
  48. if ((!tag && !tagInfo) || tagType) {
  49. if (tagName) prefix = token.string
  50. replaceToken = tagType
  51. var context = inner.mode.xmlCurrentContext ? inner.mode.xmlCurrentContext(inner.state) : []
  52. var inner = context.length && context[context.length - 1]
  53. var curTag = inner && tags[inner]
  54. var childList = inner ? curTag && curTag.children : tags['!top']
  55. if (childList && tagType != 'close') {
  56. for (var i = 0; i < childList.length; ++i) if (!prefix || matches(childList[i], prefix, matchInMiddle)) result.push('<' + childList[i])
  57. } else if (tagType != 'close') {
  58. for (var name in tags) if (tags.hasOwnProperty(name) && name != '!top' && name != '!attrs' && (!prefix || matches(name, prefix, matchInMiddle))) result.push('<' + name)
  59. }
  60. if (inner && (!prefix || (tagType == 'close' && matches(inner, prefix, matchInMiddle)))) result.push('</' + inner + '>')
  61. } else {
  62. // Attribute completion
  63. var curTag = tagInfo && tags[tagInfo.name],
  64. attrs = curTag && curTag.attrs
  65. var globalAttrs = tags['!attrs']
  66. if (!attrs && !globalAttrs) return
  67. if (!attrs) {
  68. attrs = globalAttrs
  69. } else if (globalAttrs) {
  70. // Combine tag-local and global attributes
  71. var set = {}
  72. for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm]
  73. for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm]
  74. attrs = set
  75. }
  76. if (token.type == 'string' || token.string == '=') {
  77. // A value
  78. var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)), Pos(cur.line, token.type == 'string' ? token.start : token.end))
  79. var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/),
  80. atValues
  81. if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return
  82. if (typeof atValues == 'function') atValues = atValues.call(this, cm) // Functions can be used to supply values for autocomplete widget
  83. if (token.type == 'string') {
  84. prefix = token.string
  85. var n = 0
  86. if (/['"]/.test(token.string.charAt(0))) {
  87. quote = token.string.charAt(0)
  88. prefix = token.string.slice(1)
  89. n++
  90. }
  91. var len = token.string.length
  92. if (/['"]/.test(token.string.charAt(len - 1))) {
  93. quote = token.string.charAt(len - 1)
  94. prefix = token.string.substr(n, len - 2)
  95. }
  96. if (n) {
  97. // an opening quote
  98. var line = cm.getLine(cur.line)
  99. if (line.length > token.end && line.charAt(token.end) == quote) token.end++ // include a closing quote
  100. }
  101. replaceToken = true
  102. }
  103. var returnHintsFromAtValues = function (atValues) {
  104. if (atValues) for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle)) result.push(quote + atValues[i] + quote)
  105. return returnHints()
  106. }
  107. if (atValues && atValues.then) return atValues.then(returnHintsFromAtValues)
  108. return returnHintsFromAtValues(atValues)
  109. } else {
  110. // An attribute name
  111. if (token.type == 'attribute') {
  112. prefix = token.string
  113. replaceToken = true
  114. }
  115. for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || matches(attr, prefix, matchInMiddle))) result.push(attr)
  116. }
  117. }
  118. function returnHints() {
  119. return {
  120. list: result,
  121. from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
  122. to: replaceToken ? Pos(cur.line, token.end) : cur,
  123. }
  124. }
  125. return returnHints()
  126. }
  127. CodeMirror.registerHelper('hint', 'xml', getHints)
  128. })