function _extractAttrVal(ctx) {
var attrValue = ctx.token.string,
startChar = attrValue.charAt(0),
endChar = attrValue.charAt(attrValue.length - 1),
offset = TokenUtils.offsetInToken(ctx),
foundEqualSign = false;
//If this is a fully quoted value, return the whole
//thing regardless of position
if (attrValue.length > 1 &&
(startChar === "'" || startChar === '"') &&
endChar === startChar) {
// Find an equal sign before the end quote. If found,
// then the user may be entering an attribute value right before
// another attribute and we're getting a false balanced string.
// An example of this case is where the
// cursor is right after the first double quote.
foundEqualSign = (attrValue.match(/\=\s*['"]$/) !== null);
if (!foundEqualSign) {
//strip the quotes and return;
attrValue = attrValue.substring(1, attrValue.length - 1);
offset = offset - 1 > attrValue.length ? attrValue.length : offset - 1;
return {val: attrValue, offset: offset, quoteChar: startChar, hasEndQuote: true};
}
}
if (foundEqualSign) {
var spaceIndex = attrValue.indexOf(" "),
bracketIndex = attrValue.indexOf(">"),
upToIndex = (spaceIndex !== -1 && spaceIndex < bracketIndex) ? spaceIndex : bracketIndex;
attrValue = attrValue.substring(0, (upToIndex > offset) ? upToIndex : offset);
} else if (offset > 0 && (startChar === "'" || startChar === '"')) {
//The att value is getting edit in progress. There is possible extra
//stuff in this token state since the quote isn't closed, so we assume
//the stuff from the quote to the current pos is definitely in the attribute
//value.
attrValue = attrValue.substring(0, offset);
}
//If the attrValue start with a quote, trim that now
startChar = attrValue.charAt(0);
if (startChar === "'" || startChar === '"') {
attrValue = attrValue.substring(1);
offset--;
} else {
startChar = "";
// Make attr value empty and set offset to zero if it has the ">"
// which is the closing of the tag.
if (endChar === ">") {
attrValue = "";
offset = 0;
}
}
return {val: attrValue, offset: offset, quoteChar: startChar, hasEndQuote: false};
}
function _extractTagName(ctx) {
var mode = ctx.editor.getMode(),
innerModeData = CodeMirror.innerMode(mode, ctx.token.state);
if (ctx.token.type === "tag bracket") {
return innerModeData.state.tagName;
}
// If the ctx is inside the tag name of an end tag, innerModeData.state.tagName is
// undefined. So return token string as the tag name.
return innerModeData.state.tagName || ctx.token.string;
}
function _getTagInfoStartingFromAttrName(ctx, isPriorAttr) {
//Verify We're in the attribute name, move forward and try to extract the rest of
//the info. If the user it typing the attr the rest might not be here
if (isPriorAttr === false && ctx.token.type !== "attribute") {
return createTagInfo();
}
var tagName = _extractTagName(ctx);
var attrName = ctx.token.string;
var offset = TokenUtils.offsetInToken(ctx);
if (!TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) || ctx.token.string !== "=") {
// If we're checking for a prior attribute and the next token we get is a tag or an html comment or
// an undefined token class, then we've already scanned past our original cursor location.
// So just return an empty tag info.
if (isPriorAttr &&
(!ctx.token.type ||
(ctx.token.type && ctx.token.type !== "attribute" &&
ctx.token.type.indexOf("error") === -1 &&
ctx.token.string.indexOf("<") !== -1))) {
return createTagInfo();
}
return createTagInfo(ATTR_NAME, offset, tagName, attrName);
}
if (!TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx)) {
return createTagInfo(ATTR_NAME, offset, tagName, attrName);
}
//this should be the attrvalue
var attrInfo = _extractAttrVal(ctx),
attrVal = attrInfo.val,
quoteChar = attrInfo.quoteChar,
hasEndQuote = attrInfo.hasEndQuote;
return createTagInfo(ATTR_NAME, offset, tagName, attrName, attrVal, true, quoteChar, hasEndQuote);
}
function _getTagInfoStartingFromAttrValue(ctx) {
// Assume we in the attr value
// and validate that by going backwards
var attrInfo = _extractAttrVal(ctx),
attrVal = attrInfo.val,
offset = attrInfo.offset,
quoteChar = attrInfo.quoteChar,
hasEndQuote = attrInfo.hasEndQuote,
strLength = ctx.token.string.length;
if ((ctx.token.type === "string" || ctx.token.type === "error") &&
ctx.pos.ch === ctx.token.end && strLength > 1) {
var firstChar = ctx.token.string[0],
lastChar = ctx.token.string[strLength - 1];
// We get here only when the cursor is immediately on the right of the end quote
// of an attribute value. So we want to return an empty tag info so that the caller
// can dismiss the code hint popup if it is still open.
if (firstChar === lastChar && (firstChar === "'" || firstChar === "\"")) {
return createTagInfo();
}
}
//Skip all the 'string' tokens backwards. Required to reach to the first line
//of multiline HTML attribute value.
while (TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx)) {
if (ctx.token.type !== "string") {
break;
}
}
//As we have skipped all the string tokens, make a forward navigation to move to the
//first 'string token so that in next backward navigation we can find '='.
TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx);
//Move to the prev token, and check if it's "="
if (!TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx) || ctx.token.string !== "=") {
return createTagInfo();
}
//Move to the prev token, and check if it's an attribute
if (!TokenUtils.moveSkippingWhitespace(TokenUtils.movePrevToken, ctx) || ctx.token.type !== "attribute") {
return createTagInfo();
}
var attrName = ctx.token.string;
var tagName = _extractTagName(ctx);
//We're good.
return createTagInfo(ATTR_VALUE, offset, tagName, attrName, attrVal, true, quoteChar, hasEndQuote);
}
Creates a tagInfo object and assures all the values are entered or are empty strings
function createTagInfo(tokenType, offset, tagName, attrName, attrValue, valueAssigned, quoteChar, hasEndQuote) {
return { tagName: tagName || "",
attr:
{ name: attrName || "",
value: attrValue || "",
valueAssigned: valueAssigned || false,
quoteChar: quoteChar || "",
hasEndQuote: hasEndQuote || false },
position:
{ tokenType: tokenType || "",
offset: offset || 0 } };
}
Returns an Array of info about all blocks whose token mode name matches that passed in, in the given Editor's HTML document (assumes the Editor contains HTML text).
function findBlocks(editor, modeName) {
// Start scanning from beginning of file
var ctx = TokenUtils.getInitialContext(editor._codeMirror, {line: 0, ch: 0}),
blocks = [],
currentBlock = null,
inBlock = false,
outerMode = editor._codeMirror.getMode(),
tokenModeName,
previousMode;
while (TokenUtils.moveNextToken(ctx, false)) {
tokenModeName = CodeMirror.innerMode(outerMode, ctx.token.state).mode.name;
if (inBlock) {
if (!currentBlock.end) {
// Handle empty blocks
currentBlock.end = currentBlock.start;
}
// Check for end of this block
if (tokenModeName === previousMode) {
// currentBlock.end is already set to pos of the last token by now
currentBlock.text = editor.document.getRange(currentBlock.start, currentBlock.end);
inBlock = false;
} else {
currentBlock.end = { line: ctx.pos.line, ch: ctx.pos.ch };
}
} else {
// Check for start of a block
if (tokenModeName === modeName) {
currentBlock = {
start: { line: ctx.pos.line, ch: ctx.pos.ch }
};
blocks.push(currentBlock);
inBlock = true;
} else {
previousMode = tokenModeName;
}
// else, random token: ignore
}
}
return blocks;
}