diff --git a/src/components/FileEditor/index.js b/src/components/FileEditor/index.js index 950d7de1..56c263da 100644 --- a/src/components/FileEditor/index.js +++ b/src/components/FileEditor/index.js @@ -18,12 +18,12 @@ import 'codemirror/addon/search/jump-to-line' import 'codemirror/addon/dialog/dialog' import 'codemirror/addon/scroll/annotatescrollbar' import 'codemirror/addon/search/matchesonscrollbar' +import 'codemirror/addon/hint/show-hint' +import 'codemirror/addon/hint/xml-hint' import 'codemirror/mode/xml/xml' import 'codemirror/addon/lint/lint' import 'helpers/codemirror-util-autoformat' -import 'helpers/codemirror-util-xml-hint' -import 'helpers/codemirror-util-show-hint' import isOldSyntax from 'helpers/detectOldMJMLSyntax' @@ -123,9 +123,6 @@ class FileEditor extends Component { if (prevProps.lightTheme !== this.props.lightTheme) { this._codeMirror.setOption('theme', this.props.lightTheme ? 'neo' : 'one-dark') } - if (prevProps.snippets !== this.props.snippets) { - this.initEditor() - } if (prevProps.useTab !== this.props.useTab) { this._codeMirror.setOption('indentWithTabs', this.props.useTab) } @@ -189,7 +186,6 @@ class FileEditor extends Component { wrapLines, highlightTag, lightTheme, - snippets, useTab, tabSize, indentSize, @@ -226,12 +222,13 @@ class FileEditor extends Component { "' '": cm => completeIfInTag(CodeMirror, cm), "'='": cm => completeIfInTag(CodeMirror, cm), 'Ctrl-Space': 'autocomplete', - "'+'": cm => completeAfterSnippet(CodeMirror, cm, snippets), /* eslint-enable quotes */ }, lint: this.handleValidate, }) + this._codeMirror.on('keydown', (cm, e) => this.handleKey(cm, e)) + this._codeMirror.on('change', this.handleChange) } @@ -245,6 +242,14 @@ class FileEditor extends Component { })) } + handleKey = (cm, e) => { + const { snippets } = this.props + if (e.key === 'Tab') { + e.preventDefault() + return completeAfterSnippet(CodeMirror, cm, snippets) + } + } + handleChange = debounce(async () => { const { setPreview, fileName, mjmlEngine } = this.props const mjml = this._codeMirror.getValue() diff --git a/src/components/SettingsModal/index.js b/src/components/SettingsModal/index.js index 19d66c99..f77398e9 100644 --- a/src/components/SettingsModal/index.js +++ b/src/components/SettingsModal/index.js @@ -222,7 +222,7 @@ class SettingsModal extends Component {

{'Create and manage code snippets'}

-

{'Trigger snippets by typing "+" in the text editor'}

+

{'Type a trigger and hit tab to expand it in the editor'}

diff --git a/src/components/SnippetForm/index.js b/src/components/SnippetForm/index.js index 06813ab4..bcab81a3 100644 --- a/src/components/SnippetForm/index.js +++ b/src/components/SnippetForm/index.js @@ -16,7 +16,14 @@ import { addSnippet, updateSnippet } from 'actions/snippets' }, ) class SnippetForm extends Component { + static defaultProps = { + name: '', + trigger: '', + } + state = { + snippetName: '', + snippetTrigger: '', snippetNameIsAvailable: true, snippetTriggerIsAvailable: true, snippetWasEdited: false, diff --git a/src/components/SnippetsList/SnippetItem.js b/src/components/SnippetsList/SnippetItem.js index d64a2cb4..8983eb7d 100644 --- a/src/components/SnippetsList/SnippetItem.js +++ b/src/components/SnippetsList/SnippetItem.js @@ -1,8 +1,9 @@ import React, { Component } from 'react' import { connect } from 'react-redux' -import IconEdit from 'react-icons/md/mode-edit' -import IconClose from 'react-icons/md/close' +import IconExpandMore from 'react-icons/md/expand-more' +import IconExpandLess from 'react-icons/md/expand-less' +import IconDelete from 'react-icons/md/delete' import { loadSnippet, deleteSnippet } from 'actions/snippets' @@ -47,10 +48,11 @@ class SnippetItem extends Component {
- + {snippetIsEdited && } + {!snippetIsEdited && }
this.handleDelete(name)} className="action action-remove"> - +
diff --git a/src/components/SnippetsList/style.scss b/src/components/SnippetsList/style.scss index 3a4d06f1..9a317128 100644 --- a/src/components/SnippetsList/style.scss +++ b/src/components/SnippetsList/style.scss @@ -7,6 +7,9 @@ padding: 0 20px 40px 20px; height: 600px; overflow-y: auto; + scrollbar { + display: none; + } } .SnippetItem { diff --git a/src/helpers/codemirror-autocomplete-snippets.js b/src/helpers/codemirror-autocomplete-snippets.js index 170a1521..d560e636 100644 --- a/src/helpers/codemirror-autocomplete-snippets.js +++ b/src/helpers/codemirror-autocomplete-snippets.js @@ -1,19 +1,30 @@ -/* eslint-disable */ - export function completeAfterSnippet(CodeMirror, cm, snippets) { const triggers = {} snippets.map(e => { - return (triggers['+' + e.trigger] = e.content) + return (triggers[e.trigger] = e.content) }) - setTimeout(function() { - if (!cm.state.completionActive) - cm.showHint({ - completeSingle: false, - schemaInfo: triggers, - tags: 'snippets', - }) - }, 100) + const trigger = cm.findWordAt(cm.getCursor()) + const range = cm.getRange(trigger.anchor, trigger.head) + + if (triggers[range]) { + cm.replaceRange( + triggers[range], + { + ch: trigger.anchor.ch, + line: trigger.anchor.line, + }, + { + ch: trigger.head.ch, + line: trigger.head.line, + }, + ) + } else { + cm.replaceRange('\t', { + ch: trigger.head.ch, + line: trigger.head.line, + }) + } return CodeMirror.Pass } diff --git a/src/helpers/codemirror-util-show-hint.js b/src/helpers/codemirror-util-show-hint.js deleted file mode 100644 index 4f9e171f..00000000 --- a/src/helpers/codemirror-util-show-hint.js +++ /dev/null @@ -1,530 +0,0 @@ -/* eslint-disable */ -import CodeMirror from 'codemirror' -;(function(mod) { - if (typeof exports == 'object' && typeof module == 'object') - // CommonJS - mod(require('codemirror/lib/codemirror')) - else if (typeof define == 'function' && define.amd) - // AMD - define(['../../lib/codemirror'], mod) // Plain browser env - else mod(CodeMirror) -})(function(CodeMirror) { - 'use strict' - - var HINT_ELEMENT_CLASS = 'CodeMirror-hint' - var ACTIVE_HINT_ELEMENT_CLASS = 'CodeMirror-hint-active' - - // This is the old interface, kept around for now to stay - // backwards-compatible. - CodeMirror.showHint = function(cm, getHints, options) { - if (!getHints) return cm.showHint(options) - if (options && options.async) getHints.async = true - var newOpts = { hint: getHints } - if (options) for (var prop in options) newOpts[prop] = options[prop] - return cm.showHint(newOpts) - } - - CodeMirror.defineExtension('showHint', function(options) { - options = parseOptions(this, this.getCursor('start'), options) - var selections = this.listSelections() - if (selections.length > 1) return - // By default, don't allow completion when something is selected. - // A hint function can have a `supportsSelection` property to - // indicate that it can handle selections. - if (this.somethingSelected()) { - if (!options.hint.supportsSelection) return - // Don't try with cross-line selections - for (var i = 0; i < selections.length; i++) - if (selections[i].head.line != selections[i].anchor.line) return - } - - if (this.state.completionActive) this.state.completionActive.close() - var completion = (this.state.completionActive = new Completion(this, options)) - if (!completion.options.hint) return - - CodeMirror.signal(this, 'startCompletion', this) - completion.update(true) - }) - - function Completion(cm, options) { - this.cm = cm - this.options = options - this.widget = null - this.debounce = 0 - this.tick = 0 - this.startPos = this.cm.getCursor('start') - this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length - - var self = this - cm.on( - 'cursorActivity', - (this.activityFunc = function() { - self.cursorActivity() - }), - ) - } - - var requestAnimationFrame = - window.requestAnimationFrame || - function(fn) { - return setTimeout(fn, 1000 / 60) - } - var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout - - Completion.prototype = { - close: function() { - if (!this.active()) return - this.cm.state.completionActive = null - this.tick = null - this.cm.off('cursorActivity', this.activityFunc) - - if (this.widget && this.data) CodeMirror.signal(this.data, 'close') - if (this.widget) this.widget.close() - CodeMirror.signal(this.cm, 'endCompletion', this.cm) - }, - - active: function() { - return this.cm.state.completionActive == this - }, - - pick: function(data, i) { - var completion = data.list[i] - if (completion.hint) { - completion.hint(this.cm, data, completion) - } else if (this.options.tags == 'snippets') { - completion = this.options.schemaInfo[data.list[i]] - this.cm.replaceRange( - getText(completion), - completion.from || data.from, - completion.to || data.to, - 'complete', - ) - } else { - this.cm.replaceRange( - getText(completion), - completion.from || data.from, - completion.to || data.to, - 'complete', - ) - } - CodeMirror.signal(data, 'pick', completion) - this.close() - }, - - cursorActivity: function() { - if (this.debounce) { - cancelAnimationFrame(this.debounce) - this.debounce = 0 - } - - var pos = this.cm.getCursor(), - line = this.cm.getLine(pos.line) - if ( - pos.line != this.startPos.line || - line.length - pos.ch != this.startLen - this.startPos.ch || - pos.ch < this.startPos.ch || - this.cm.somethingSelected() || - (pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1))) - ) { - this.close() - } else { - var self = this - this.debounce = requestAnimationFrame(function() { - self.update() - }) - if (this.widget) this.widget.disable() - } - }, - - update: function(first) { - if (this.tick == null) return - var self = this, - myTick = ++this.tick - fetchHints(this.options.hint, this.cm, this.options, function(data) { - if (self.tick == myTick) self.finishUpdate(data, first) - }) - }, - - finishUpdate: function(data, first) { - if (this.data) CodeMirror.signal(this.data, 'update') - - var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle) - if (this.widget) this.widget.close() - - this.data = data - - if (data && data.list.length) { - if (picked && data.list.length == 1) { - this.pick(data, 0) - } else { - this.widget = new Widget(this, data) - CodeMirror.signal(data, 'shown') - } - } - }, - } - - function parseOptions(cm, pos, options) { - var editor = cm.options.hintOptions - var out = {} - for (var prop in defaultOptions) out[prop] = defaultOptions[prop] - if (editor) for (var prop in editor) if (editor[prop] !== undefined) out[prop] = editor[prop] - if (options) - for (var prop in options) if (options[prop] !== undefined) out[prop] = options[prop] - if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos) - return out - } - - function getText(completion) { - if (typeof completion == 'string') return completion - else return completion.text - } - - function buildKeyMap(completion, handle) { - var baseMap = { - Up: function() { - handle.moveFocus(-1) - }, - Down: function() { - handle.moveFocus(1) - }, - PageUp: function() { - handle.moveFocus(-handle.menuSize() + 1, true) - }, - PageDown: function() { - handle.moveFocus(handle.menuSize() - 1, true) - }, - Home: function() { - handle.setFocus(0) - }, - End: function() { - handle.setFocus(handle.length - 1) - }, - Enter: handle.pick, - Tab: handle.pick, - Esc: handle.close, - } - var custom = completion.options.customKeys - var ourMap = custom ? {} : baseMap - function addBinding(key, val) { - var bound - if (typeof val != 'string') - bound = function(cm) { - return val(cm, handle) - } - else if (baseMap.hasOwnProperty(val)) - // This mechanism is deprecated - bound = baseMap[val] - else bound = val - ourMap[key] = bound - } - if (custom) for (var key in custom) if (custom.hasOwnProperty(key)) addBinding(key, custom[key]) - var extra = completion.options.extraKeys - if (extra) for (var key in extra) if (extra.hasOwnProperty(key)) addBinding(key, extra[key]) - return ourMap - } - - function getHintElement(hintsElement, el) { - while (el && el != hintsElement) { - if (el.nodeName.toUpperCase() === 'LI' && el.parentNode == hintsElement) return el - el = el.parentNode - } - } - - function Widget(completion, data) { - this.completion = completion - this.data = data - this.picked = false - var widget = this, - cm = completion.cm - - var hints = (this.hints = document.createElement('ul')) - hints.className = 'CodeMirror-hints' - this.selectedHint = data.selectedHint || 0 - - var completions = data.list - for (var i = 0; i < completions.length; ++i) { - var elt = hints.appendChild(document.createElement('li')), - cur = completions[i] - var className = - HINT_ELEMENT_CLASS + (i != this.selectedHint ? '' : ' ' + ACTIVE_HINT_ELEMENT_CLASS) - if (cur.className != null) className = cur.className + ' ' + className - elt.className = className - if (cur.render) cur.render(elt, data, cur) - else elt.appendChild(document.createTextNode(cur.displayText || getText(cur))) - elt.hintId = i - } - - var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null) - var left = pos.left, - top = pos.bottom, - below = true - hints.style.left = left + 'px' - hints.style.top = top + 'px' - // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. - var winW = - window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth) - var winH = - window.innerHeight || - Math.max(document.body.offsetHeight, document.documentElement.offsetHeight) - ;(completion.options.container || document.body).appendChild(hints) - var box = hints.getBoundingClientRect(), - overlapY = box.bottom - winH - var scrolls = hints.scrollHeight > hints.clientHeight + 1 - var startScroll = cm.getScrollInfo() - - if (overlapY > 0) { - var height = box.bottom - box.top, - curTop = pos.top - (pos.bottom - box.top) - if (curTop - height > 0) { - // Fits above cursor - hints.style.top = (top = pos.top - height) + 'px' - below = false - } else if (height > winH) { - hints.style.height = winH - 5 + 'px' - hints.style.top = (top = pos.bottom - box.top) + 'px' - var cursor = cm.getCursor() - if (data.from.ch != cursor.ch) { - pos = cm.cursorCoords(cursor) - hints.style.left = (left = pos.left) + 'px' - box = hints.getBoundingClientRect() - } - } - } - var overlapX = box.right - winW - if (overlapX > 0) { - if (box.right - box.left > winW) { - hints.style.width = winW - 5 + 'px' - overlapX -= box.right - box.left - winW - } - hints.style.left = (left = pos.left - overlapX) + 'px' - } - if (scrolls) - for (var node = hints.firstChild; node; node = node.nextSibling) - node.style.paddingRight = cm.display.nativeBarWidth + 'px' - - cm.addKeyMap( - (this.keyMap = buildKeyMap(completion, { - moveFocus: function(n, avoidWrap) { - widget.changeActive(widget.selectedHint + n, avoidWrap) - }, - setFocus: function(n) { - widget.changeActive(n) - }, - menuSize: function() { - return widget.screenAmount() - }, - length: completions.length, - close: function() { - completion.close() - }, - pick: function() { - widget.pick() - }, - data: data, - })), - ) - - if (completion.options.closeOnUnfocus) { - var closingOnBlur - cm.on( - 'blur', - (this.onBlur = function() { - closingOnBlur = setTimeout(function() { - completion.close() - }, 100) - }), - ) - cm.on( - 'focus', - (this.onFocus = function() { - clearTimeout(closingOnBlur) - }), - ) - } - - cm.on( - 'scroll', - (this.onScroll = function() { - var curScroll = cm.getScrollInfo(), - editor = cm.getWrapperElement().getBoundingClientRect() - var newTop = top + startScroll.top - curScroll.top - var point = - newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop) - if (!below) point += hints.offsetHeight - if (point <= editor.top || point >= editor.bottom) return completion.close() - hints.style.top = newTop + 'px' - hints.style.left = left + startScroll.left - curScroll.left + 'px' - }), - ) - - CodeMirror.on(hints, 'dblclick', function(e) { - var t = getHintElement(hints, e.target || e.srcElement) - if (t && t.hintId != null) { - widget.changeActive(t.hintId) - widget.pick() - } - }) - - CodeMirror.on(hints, 'click', function(e) { - var t = getHintElement(hints, e.target || e.srcElement) - if (t && t.hintId != null) { - widget.changeActive(t.hintId) - if (completion.options.completeOnSingleClick) widget.pick() - } - }) - - CodeMirror.on(hints, 'mousedown', function() { - setTimeout(function() { - cm.focus() - }, 20) - }) - - CodeMirror.signal( - data, - 'select', - completions[this.selectedHint], - hints.childNodes[this.selectedHint], - ) - return true - } - - Widget.prototype = { - close: function() { - if (this.completion.widget != this) return - this.completion.widget = null - this.hints.parentNode.removeChild(this.hints) - this.completion.cm.removeKeyMap(this.keyMap) - - var cm = this.completion.cm - if (this.completion.options.closeOnUnfocus) { - cm.off('blur', this.onBlur) - cm.off('focus', this.onFocus) - } - cm.off('scroll', this.onScroll) - }, - - disable: function() { - this.completion.cm.removeKeyMap(this.keyMap) - var widget = this - this.keyMap = { - Enter: function() { - widget.picked = true - }, - } - this.completion.cm.addKeyMap(this.keyMap) - }, - - pick: function() { - this.completion.pick(this.data, this.selectedHint) - }, - - changeActive: function(i, avoidWrap) { - if (i >= this.data.list.length) i = avoidWrap ? this.data.list.length - 1 : 0 - else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1 - if (this.selectedHint == i) return - var node = this.hints.childNodes[this.selectedHint] - node.className = node.className.replace(' ' + ACTIVE_HINT_ELEMENT_CLASS, '') - node = this.hints.childNodes[(this.selectedHint = i)] - node.className += ' ' + ACTIVE_HINT_ELEMENT_CLASS - if (node.offsetTop < this.hints.scrollTop) this.hints.scrollTop = node.offsetTop - 3 - else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) - this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3 - CodeMirror.signal(this.data, 'select', this.data.list[this.selectedHint], node) - }, - - screenAmount: function() { - return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1 - }, - } - - function applicableHelpers(cm, helpers) { - if (!cm.somethingSelected()) return helpers - var result = [] - for (var i = 0; i < helpers.length; i++) - if (helpers[i].supportsSelection) result.push(helpers[i]) - return result - } - - function fetchHints(hint, cm, options, callback) { - if (hint.async) { - hint(cm, callback, options) - } else { - var result = hint(cm, options) - if (result && result.then) result.then(callback) - else callback(result) - } - } - - function resolveAutoHints(cm, pos) { - var helpers = cm.getHelpers(pos, 'hint'), - words - if (helpers.length) { - var resolved = function(cm, callback, options) { - var app = applicableHelpers(cm, helpers) - function run(i) { - if (i == app.length) return callback(null) - fetchHints(app[i], cm, options, function(result) { - if (result && result.list.length > 0) { - callback(result) - } else run(i + 1) - }) - } - run(0) - } - resolved.async = true - resolved.supportsSelection = true - return resolved - } else if ((words = cm.getHelper(cm.getCursor(), 'hintWords'))) { - return function(cm) { - return CodeMirror.hint.fromList(cm, { words: words }) - } - } else if (CodeMirror.hint.anyword) { - return function(cm, options) { - return CodeMirror.hint.anyword(cm, options) - } - } else { - return function() {} - } - } - - CodeMirror.registerHelper('hint', 'auto', { - resolve: resolveAutoHints, - }) - - CodeMirror.registerHelper('hint', 'fromList', function(cm, options) { - var cur = cm.getCursor(), - token = cm.getTokenAt(cur) - var to = CodeMirror.Pos(cur.line, token.end) - if (token.string && /\w/.test(token.string[token.string.length - 1])) { - var term = token.string, - from = CodeMirror.Pos(cur.line, token.start) - } else { - var term = '', - from = to - } - var found = [] - for (var i = 0; i < options.words.length; i++) { - var word = options.words[i] - if (word.slice(0, term.length) == term) found.push(word) - } - - if (found.length) return { list: found, from: from, to: to } - }) - - CodeMirror.commands.autocomplete = CodeMirror.showHint - - var defaultOptions = { - hint: CodeMirror.hint.auto, - completeSingle: true, - alignWithWord: true, - closeCharacters: /[\s()\[\]{};:>,]/, - closeOnUnfocus: true, - completeOnSingleClick: true, - container: null, - customKeys: null, - extraKeys: null, - } - - CodeMirror.defineOption('hintOptions', null) -}) diff --git a/src/helpers/codemirror-util-xml-hint.js b/src/helpers/codemirror-util-xml-hint.js deleted file mode 100644 index 3393085e..00000000 --- a/src/helpers/codemirror-util-xml-hint.js +++ /dev/null @@ -1,147 +0,0 @@ -/* eslint-disable */ -import CodeMirror from 'codemirror' -;(function(mod) { - if (typeof exports == 'object' && typeof module == 'object') - // CommonJS - mod(require('codemirror/lib/codemirror')) - else if (typeof define == 'function' && define.amd) - // AMD - define(['../../lib/codemirror'], mod) // Plain browser env - else mod(CodeMirror) -})(function(CodeMirror) { - 'use strict' - - var Pos = CodeMirror.Pos - - function getHints(cm, options) { - var tags = options && options.schemaInfo - var quote = (options && options.quoteChar) || '"' - if (!tags) return - var cur = cm.getCursor(), - token = cm.getTokenAt(cur) - token.type = token.type || 'trigger' - if (token.end > cur.ch) { - token.end = cur.ch - token.string = token.string.slice(0, cur.ch - token.start) - } - var inner = CodeMirror.innerMode(cm.getMode(), token.state) - if (inner.mode.name != 'xml') return - var result = [], - replaceToken = false, - prefix - var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string) - var tagName = tag && /^\w/.test(token.string) - var tagStart - var trigger = token.type == 'trigger' - var triggerName = trigger - - if (tagName) { - var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start) - var tagType = /<\/$/.test(before) ? 'close' : /<$/.test(before) ? 'open' : null - if (tagType) tagStart = token.start - (tagType == 'close' ? 2 : 1) - } else if (tag && token.string == '<') { - tagType = 'open' - } else if (tag && token.string == '') - } else if (((!tag && !inner.state.tagName) || triggerType) && options.tags == 'snippets') { - if (triggerName) prefix = token.string - replaceToken = triggerType - - for (var name in tags) - if (tags.hasOwnProperty(name) && (!prefix || name.lastIndexOf(prefix, 0) == 0)) { - replaceToken = true - result.push(name) - } - } else { - // Attribute completion - var curTag = tags[inner.state.tagName], - attrs = curTag && curTag.attrs - var globalAttrs = tags['!attrs'] - if (!attrs && !globalAttrs) return - if (!attrs) { - attrs = globalAttrs - } else if (globalAttrs) { - // Combine tag-local and global attributes - var set = {} - for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm] - for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm] - attrs = set - } - if (token.type == 'string' || token.string == '=') { - // A value - var before = cm.getRange( - Pos(cur.line, Math.max(0, cur.ch - 60)), - Pos(cur.line, token.type == 'string' ? token.start : token.end), - ) - var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), - atValues - if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return - if (typeof atValues == 'function') atValues = atValues.call(this, cm) // Functions can be used to supply values for autocomplete widget - if (token.type == 'string') { - prefix = token.string - var n = 0 - if (/['"]/.test(token.string.charAt(0))) { - quote = token.string.charAt(0) - prefix = token.string.slice(1) - n++ - } - var len = token.string.length - if (/['"]/.test(token.string.charAt(len - 1))) { - quote = token.string.charAt(len - 1) - prefix = token.string.substr(n, len - 2) - } - replaceToken = true - } - for (var i = 0; i < atValues.length; ++i) - if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0) - result.push(quote + atValues[i] + quote) - } else { - // An attribute name - if (token.type == 'attribute') { - prefix = token.string - replaceToken = true - } - for (var attr in attrs) - if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0)) - result.push(attr) - } - } - return { - list: result, - from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur, - to: replaceToken ? Pos(cur.line, token.end) : cur, - } - } - - CodeMirror.registerHelper('hint', 'xml', getHints) -})