Merge pull request #225 from toasted-nutbread/text-source-range-improvements

TextSourceRange improvements
This commit is contained in:
Alex Yatskov 2019-09-30 19:58:19 -07:00 committed by GitHub
commit fba2bef905
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -83,120 +83,142 @@ class TextSourceRange {
} }
static shouldEnter(node) { static shouldEnter(node) {
if (node.nodeType !== 1) { switch (node.nodeName.toUpperCase()) {
return false; case 'RT':
} case 'SCRIPT':
case 'STYLE':
const skip = ['RT', 'SCRIPT', 'STYLE']; return false;
if (skip.includes(node.nodeName.toUpperCase())) {
return false;
} }
const style = window.getComputedStyle(node); const style = window.getComputedStyle(node);
const hidden = return !(
style.visibility === 'hidden' || style.visibility === 'hidden' ||
style.display === 'none' || style.display === 'none' ||
parseFloat(style.fontSize) === 0; parseFloat(style.fontSize) === 0);
}
return !hidden; static getRubyElement(node) {
node = TextSourceRange.getParentElement(node);
if (node !== null && node.nodeName.toUpperCase() === "RT") {
node = node.parentNode;
return (node !== null && node.nodeName.toUpperCase() === "RUBY") ? node : null;
}
return null;
} }
static seekForward(node, offset, length) { static seekForward(node, offset, length) {
const state = {node, offset, remainder: length, content: ''}; const state = {node, offset, remainder: length, content: ''};
if (!TextSourceRange.seekForwardHelper(node, state)) { const TEXT_NODE = Node.TEXT_NODE;
return state; const ELEMENT_NODE = Node.ELEMENT_NODE;
let resetOffset = false;
const ruby = TextSourceRange.getRubyElement(node);
if (ruby !== null) {
node = ruby;
resetOffset = true;
} }
for (let current = node; current !== null; current = current.parentElement) { while (node !== null) {
for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { let visitChildren = true;
if (!TextSourceRange.seekForwardHelper(sibling, state)) { const nodeType = node.nodeType;
return state;
if (nodeType === TEXT_NODE) {
state.node = node;
if (TextSourceRange.seekForwardTextNode(state, resetOffset)) {
break;
} }
resetOffset = true;
} else if (nodeType === ELEMENT_NODE) {
visitChildren = TextSourceRange.shouldEnter(node);
} }
node = TextSourceRange.getNextNode(node, visitChildren);
} }
return state; return state;
} }
static seekForwardHelper(node, state) { static seekForwardTextNode(state, resetOffset) {
if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { const nodeValue = state.node.nodeValue;
const offset = state.node === node ? state.offset : 0; const nodeValueLength = nodeValue.length;
let content = state.content;
let offset = resetOffset ? 0 : state.offset;
let remainder = state.remainder;
let result = false;
let consumed = 0; for (; offset < nodeValueLength; ++offset) {
let stripped = 0; const c = nodeValue[offset];
while (state.remainder - consumed > 0) { if (!IGNORE_TEXT_PATTERN.test(c)) {
const currentChar = node.nodeValue[offset + consumed + stripped]; content += c;
if (!currentChar) { if (--remainder <= 0) {
break; result = true;
} else if (currentChar.match(IGNORE_TEXT_PATTERN)) { ++offset;
stripped++;
} else {
consumed++;
state.content += currentChar;
}
}
state.node = node;
state.offset = offset + consumed + stripped;
state.remainder -= consumed;
} else if (TextSourceRange.shouldEnter(node)) {
for (let i = 0; i < node.childNodes.length; ++i) {
if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) {
break; break;
} }
} }
} }
return state.remainder > 0; state.offset = offset;
state.content = content;
state.remainder = remainder;
return result;
} }
static seekBackward(node, offset, length) { static seekBackward(node, offset, length) {
const state = {node, offset, remainder: length, content: ''}; const state = {node, offset, remainder: length, content: ''};
if (!TextSourceRange.seekBackwardHelper(node, state)) { const TEXT_NODE = Node.TEXT_NODE;
return state; const ELEMENT_NODE = Node.ELEMENT_NODE;
let resetOffset = false;
const ruby = TextSourceRange.getRubyElement(node);
if (ruby !== null) {
node = ruby;
resetOffset = true;
} }
for (let current = node; current !== null; current = current.parentElement) { while (node !== null) {
for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { let visitChildren = true;
if (!TextSourceRange.seekBackwardHelper(sibling, state)) { const nodeType = node.nodeType;
return state;
if (nodeType === TEXT_NODE) {
state.node = node;
if (TextSourceRange.seekBackwardTextNode(state, resetOffset)) {
break;
} }
resetOffset = true;
} else if (nodeType === ELEMENT_NODE) {
visitChildren = TextSourceRange.shouldEnter(node);
} }
node = TextSourceRange.getPreviousNode(node, visitChildren);
} }
return state; return state;
} }
static seekBackwardHelper(node, state) { static seekBackwardTextNode(state, resetOffset) {
if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { const nodeValue = state.node.nodeValue;
const offset = state.node === node ? state.offset : node.length; let content = state.content;
let offset = resetOffset ? nodeValue.length : state.offset;
let remainder = state.remainder;
let result = false;
let consumed = 0; for (; offset > 0; --offset) {
let stripped = 0; const c = nodeValue[offset - 1];
while (state.remainder - consumed > 0) { if (!IGNORE_TEXT_PATTERN.test(c)) {
const currentChar = node.nodeValue[offset - 1 - consumed - stripped]; // negative indices are undefined in JS content = c + content;
if (!currentChar) { if (--remainder <= 0) {
break; result = true;
} else if (currentChar.match(IGNORE_TEXT_PATTERN)) { --offset;
stripped++;
} else {
consumed++;
state.content = currentChar + state.content;
}
}
state.node = node;
state.offset = offset - consumed - stripped;
state.remainder -= consumed;
} else if (TextSourceRange.shouldEnter(node)) {
for (let i = node.childNodes.length - 1; i >= 0; --i) {
if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) {
break; break;
} }
} }
} }
return state.remainder > 0; state.offset = offset;
state.content = content;
state.remainder = remainder;
return result;
} }
static getParentElement(node) { static getParentElement(node) {
@ -219,22 +241,38 @@ class TextSourceRange {
static getNodesInRange(range) { static getNodesInRange(range) {
const end = range.endContainer; const end = range.endContainer;
const nodes = []; const nodes = [];
for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node)) { for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node, true)) {
nodes.push(node); nodes.push(node);
if (node === end) { break; } if (node === end) { break; }
} }
return nodes; return nodes;
} }
static getNextNode(node) { static getNextNode(node, visitChildren) {
let next = node.firstChild; let next = visitChildren ? node.firstChild : null;
if (next === null) { if (next === null) {
while (true) { while (true) {
next = node.nextSibling; next = node.nextSibling;
if (next !== null) { break; } if (next !== null) { break; }
next = node.parentNode; next = node.parentNode;
if (node === null) { break; } if (next === null) { break; }
node = next;
}
}
return next;
}
static getPreviousNode(node, visitChildren) {
let next = visitChildren ? node.lastChild : null;
if (next === null) {
while (true) {
next = node.previousSibling;
if (next !== null) { break; }
next = node.parentNode;
if (next === null) { break; }
node = next; node = next;
} }