refactor lint report stuff, use $element

This commit is contained in:
tophf 2017-08-31 22:25:05 +03:00
parent ec01914f17
commit 85a5702fe0
3 changed files with 97 additions and 70 deletions

View File

@ -154,7 +154,7 @@ function setCleanSection(section) {
const cm = section.CodeMirror; const cm = section.CodeMirror;
if (cm) { if (cm) {
section.savedValue = cm.changeGeneration(); section.savedValue = cm.changeGeneration();
indicateCodeChange(cm); updateTitle();
} }
} }
@ -1570,6 +1570,7 @@ function fromMozillaFormat() {
// parserlib contained in CSSLint-worker.js // parserlib contained in CSSLint-worker.js
onDOMscripted(['vendor-overwrites/csslint/csslint-worker.js']).then(() => { onDOMscripted(['vendor-overwrites/csslint/csslint-worker.js']).then(() => {
doImportWhenReady(event.target); doImportWhenReady(event.target);
editors.forEach(cm => updateLintReportIfEnabled(cm, 1));
editors.last.state.renderLintReportNow = true; editors.last.state.renderLintReportNow = true;
}); });
} }

View File

@ -152,9 +152,9 @@ function updateLinter({immediately} = {}) {
if (guttersOption) { if (guttersOption) {
cm.setOption('guttersOption', guttersOption); cm.setOption('guttersOption', guttersOption);
updateGutters(cm, guttersOption); updateGutters(cm, guttersOption);
cm.refresh();
} }
cm.refresh(); setTimeout(updateLintReport, 0, cm);
updateLintReport(cm);
}); });
} }
@ -188,6 +188,9 @@ function updateLinter({immediately} = {}) {
updateEditors(); updateEditors();
}); });
$('#linter-settings').style.display = !linter ? 'none' : 'inline-block'; $('#linter-settings').style.display = !linter ? 'none' : 'inline-block';
if (!linter) {
$('#lint > div').textContent = '';
}
} }
function updateLintReport(cm, delay) { function updateLintReport(cm, delay) {
@ -203,7 +206,7 @@ function updateLintReport(cm, delay) {
if (delay === 0) { if (delay === 0) {
// immediately show pending csslint/stylelint messages in onbeforeunload and save // immediately show pending csslint/stylelint messages in onbeforeunload and save
clearTimeout(state.lintTimeout); clearTimeout(state.lintTimeout);
update(cm); updateLintReportInternal(cm);
return; return;
} }
if (delay > 0) { if (delay > 0) {
@ -211,73 +214,79 @@ function updateLintReport(cm, delay) {
state.lintTimeout = setTimeout(cm => { state.lintTimeout = setTimeout(cm => {
if (cm.performLint) { if (cm.performLint) {
cm.performLint(); cm.performLint();
update(cm); updateLintReportInternal(cm);
} }
}, delay, cm); }, delay, cm);
return; return;
} }
// user is editing right now: postpone updating the report for the new issues (default: 500ms lint + 4500ms) if (state.options) {
// or update it as soon as possible (default: 500ms lint + 100ms) in case an existing issue was just fixed clearTimeout(state.reportTimeout);
clearTimeout(state.reportTimeout); const delay = cm && cm.state.renderLintReportNow ? 0 : state.options.delay + 100;
state.reportTimeout = setTimeout(update, (state.options || {}).delay + 100, cm); state.reportTimeout = setTimeout(updateLintReportInternal, delay, cm, {
state.postponeNewIssues = delay === undefined || delay === null; postponeNewIssues: delay === undefined || delay === null
});
}
}
function update(cm) { function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
const scope = cm ? [cm] : editors; scope = scope ? [scope] : editors;
let changed = false; let changed = false;
let fixedOldIssues = false; let fixedOldIssues = false;
scope.forEach(cm => { const clipString = (str, limit) =>
const scopedState = cm.state.lint || {}; str.length <= limit ? str : str.substr(0, limit) + '...';
const oldMarkers = scopedState.markedLast || {}; scope.forEach(cm => {
const newMarkers = {}; const lintState = cm.state.lint || {};
const html = !scopedState.marked || scopedState.marked.length === 0 ? '' : '<tbody>' + const oldMarkers = lintState.markedLast || new Map();
scopedState.marked.map(mark => { const newMarkers = lintState.markedLast = new Map();
const marked = lintState.marked || {};
const activeLine = cm.getCursor().line;
if (marked.length) {
const body = $element({tag: 'tbody',
appendChild: marked.map(mark => {
const info = mark.__annotation; const info = mark.__annotation;
const isActiveLine = info.from.line === cm.getCursor().line; const {line, ch} = info.from;
const pos = isActiveLine ? 'cursor' : (info.from.line + ',' + info.from.ch); const isActiveLine = line === activeLine;
// rule name added in parentheses at the end; extract it out for the info popup const pos = isActiveLine ? 'cursor' : (line + ',' + ch);
const text = info.message; const title = clipString(info.message, 1000) + `\n(${info.rule})`;
const title = escapeHtml(text + `\n(${info.rule})`, {limit: 1000}); const message = clipString(info.message, 100);
const message = escapeHtml(text, {limit: 100});
if (isActiveLine || oldMarkers[pos] === message) { if (isActiveLine || oldMarkers[pos] === message) {
delete oldMarkers[pos]; oldMarkers.delete(pos);
} }
newMarkers[pos] = message; newMarkers.set(pos, message);
return `<tr class="${info.severity}"> return $element({tag: 'tr',
<td role="severity" data-rule="${info.rule}"> className: info.severity,
<div class="CodeMirror-lint-marker-${info.severity}">${info.severity}</div> appendChild: [
</td> $element({tag: 'td',
<td role="line">${info.from.line + 1}</td> attributes: {role: 'severity'},
<td role="sep">:</td> dataset: {rule: info.rule},
<td role="col">${info.from.ch + 1}</td> appendChild: $element({
<td role="message" title="${title}">${message}</td> className: 'CodeMirror-lint-marker-' + info.severity,
</tr>`; textContent: info.severity,
}).join('') + '</tbody>'; }),
scopedState.markedLast = newMarkers; }),
fixedOldIssues |= scopedState.reportDisplayed && Object.keys(oldMarkers).length > 0; $element({tag: 'td', attributes: {role: 'line'}, textContent: line + 1}),
if ((scopedState.html || '') !== html) { $element({tag: 'td', attributes: {role: 'sep'}, textContent: ':'}),
scopedState.html = html; $element({tag: 'td', attributes: {role: 'col'}, textContent: ch + 1}),
$element({tag: 'td', attributes: {role: 'message'}, textContent: message, title}),
],
});
})
});
const text = body.textContentCached = body.textContent;
if (text !== ((lintState.body || {}).textContentCached || '')) {
lintState.body = body;
changed = true; changed = true;
} }
});
if (changed) {
clearTimeout(state ? state.renderTimeout : undefined);
if (!state || !state.postponeNewIssues || fixedOldIssues || editors.last.state.renderLintReportNow) {
editors.last.state.renderLintReportNow = false;
renderLintReport(true);
} else {
state.renderTimeout = setTimeout(renderLintReport, CodeMirror.defaults.lintReportDelay, true);
}
} }
} fixedOldIssues |= lintState.reportDisplayed && oldMarkers.size;
function escapeHtml(html, {limit} = {}) { });
const chars = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;'}; if (changed) {
let ellipsis = ''; if (!postponeNewIssues || fixedOldIssues || editors.last.state.renderLintReportNow) {
if (limit && html.length > limit) { editors.last.state.renderLintReportNow = false;
html = html.substr(0, limit); renderLintReport(true);
ellipsis = '...'; } else {
debounce(renderLintReport, CodeMirror.defaults.lintReportDelay, true);
} }
return html.replace(/[&<>"'/]/g, char => chars[char]) + ellipsis;
} }
} }
@ -288,18 +297,29 @@ function renderLintReport(someBlockChanged) {
const newContent = content.cloneNode(false); const newContent = content.cloneNode(false);
let issueCount = 0; let issueCount = 0;
editors.forEach((cm, index) => { editors.forEach((cm, index) => {
if (cm.state.lint && cm.state.lint.html) { const lintState = cm.state.lint || {};
const html = '<caption>' + label + ' ' + (index + 1) + '</caption>' + cm.state.lint.html; const body = lintState.body;
const newBlock = newContent.appendChild(tHTML(html, 'table')); if (!body) {
return;
newBlock.cm = cm;
issueCount += newBlock.rows.length;
const block = content.children[newContent.children.length - 1];
const blockChanged = !block || cm !== block.cm || html !== block.innerHTML;
someBlockChanged |= blockChanged;
cm.state.lint.reportDisplayed = blockChanged;
} }
const newBlock = $element({
tag: 'table',
appendChild: [
$element({tag: 'caption', textContent: label + ' ' + (index + 1)}),
body,
],
cm,
});
newContent.appendChild(newBlock);
issueCount += newBlock.rows.length;
const block = content.children[newContent.children.length - 1];
const blockChanged =
!block ||
block.cm !== cm ||
body.textContentCached !== block.textContentCached;
someBlockChanged |= blockChanged;
lintState.reportDisplayed = blockChanged;
}); });
if (someBlockChanged || newContent.children.length !== content.children.length) { if (someBlockChanged || newContent.children.length !== content.children.length) {
$('#issue-count').textContent = issueCount; $('#issue-count').textContent = issueCount;

View File

@ -244,6 +244,12 @@ function $element(opt) {
Object.assign(element.dataset, opt.dataset); Object.assign(element.dataset, opt.dataset);
delete opt.dataset; delete opt.dataset;
} }
if (opt.attributes) {
for (const attr in opt.attributes) {
element.setAttribute(attr, opt.attributes[attr]);
}
delete opt.attributes;
}
if (ns) { if (ns) {
for (const attr in opt) { for (const attr in opt) {
element.setAttributeNS(null, attr, opt[attr]); element.setAttributeNS(null, attr, opt[attr]);