// ==UserScript==
// @name LWN.net Code Beautifier (v1.1.26 - Reduced Logging)
// @name:zh-CN LWN.net 代码美化脚本 (v1.1.26)
// @namespace http://tampermonkey.net/
// @version 1.1.26
// @description Improves code block appearance on lwn.net with syntax highlighting and better diff formatting. Reduced logging.
// @description:zh-CN 使用语法高亮改进 LWN.net 代码块外观,优化 diff 格式。
// @author Gemini & Your Name
// @match *://lwn.net/*
// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
// @resource hljsCSS https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-light.min.css
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_log
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const SCRIPT_VERSION = 'v1.1.26';
// Keep essential startup log
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Starting...`);
// --- Global variables and state ---
let isEnhanced = GM_getValue('lwnBeautifierEnabled', true);
const originalContentMap = new Map(); // Store original <pre> content
const enhancedStylesId = 'lwn-beautifier-styles-' + Date.now();
let styleElement = null;
// --- Check Highlight.js ---
if (typeof hljs === 'undefined') {
const errorMsg = `LWN Code Beautifier (${SCRIPT_VERSION}): Highlight.js (hljs) not found!`;
console.error(errorMsg);
GM_log(errorMsg); // Keep error log
return;
} else {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Highlight.js loaded.`); // Keep confirmation log
}
// --- CSS Definitions ---
function getEnhancedCss() {
const themeCss = GM_getResourceText("hljsCSS");
// Reduced comments in CSS
const customCss = `
body.lwn-beautifier-active pre {
background-color: #fafafa !important; color: #383a42 !important;
border: 1px solid #e8e8e8 !important; padding: 1em !important;
margin-bottom: 1em !important; overflow-x: auto !important;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 14px !important; line-height: 1.45em !important;
white-space: pre !important; position: relative;
}
body.lwn-beautifier-active pre code.hljs {
padding: 0; background-color: transparent; border-radius: 0px;
display: block; overflow-x: visible; white-space: pre !important;
}
body.lwn-beautifier-active pre code.hljs span.line {
display: block;
position: relative;
}
body.lwn-beautifier-active pre code.hljs span.line.is-diff-hunk-line {
display: flex;
}
body.lwn-beautifier-active pre code.hljs span.line[data-line-type^="hunk_"]::before,
body.lwn-beautifier-active pre code.hljs span.line[data-line-type="normal"]::before {
content: ""; display: none;
}
body.lwn-beautifier-active pre code.hljs span.line.is-diff-hunk-line .diff-marker-column {
display: inline-block; width: 1.8em; text-align: center;
font-weight: bold; flex-shrink: 0; user-select: none;
}
body.lwn-beautifier-active pre code.hljs span.line.is-diff-hunk-line .diff-code-column {
flex-grow: 1; white-space: pre; display: block;
}
body.lwn-beautifier-active pre code.hljs span.line[data-line-type="hunk_addition"] .diff-marker-column { color: #50a14f; }
body.lwn-beautifier-active pre code.hljs span.line[data-line-type="hunk_deletion"] .diff-marker-column { color: #e45649; }
body.lwn-beautifier-active pre code.hljs span.line[data-line-type="hunk_context"] .diff-marker-column { color: #383a42; }
body.lwn-beautifier-active pre code.hljs span.line[data-line-type="empty_original_in_hunk"] .diff-marker-column { color: #383a42; }
body.lwn-beautifier-active pre code.hljs span.line.diff-meta-line { color: #6c757d; font-style: italic; }
body.lwn-beautifier-active pre code.hljs span.line.diff-meta-line::before { content: ""; display: none; }
`;
return themeCss + '\n' + customCss;
}
// --- Core Beautification Logic ---
function applyHighlighting() {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Applying highlighting...`); // Keep start log
document.body.classList.add('lwn-beautifier-active');
if (!styleElement || !document.head.contains(styleElement)) {
styleElement = document.createElement('style');
styleElement.id = enhancedStylesId;
document.head.appendChild(styleElement);
}
styleElement.textContent = getEnhancedCss();
const codeBlocks = document.querySelectorAll('pre');
if (codeBlocks.length === 0) {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): No <pre> elements found.`); // Keep info log
return;
}
codeBlocks.forEach((block, index) => {
// Skip non-code blocks heuristically
if (block.closest('blockquote.QuoteBody') || block.closest('.GAByline') || block.closest('.CommentTitle') || (block.innerText.length < 10 && !block.innerHTML.includes('<'))) {
return;
}
const storedHTML = originalContentMap.get(block);
// Skip if already processed and unchanged
if (block.dataset.lwnBeautifierProcessed === 'true' && storedHTML === block.innerHTML && document.body.classList.contains('lwn-beautifier-active')) {
return;
}
const initialPreHTML = storedHTML || block.innerHTML;
if (!originalContentMap.has(block) || storedHTML !== block.innerHTML) {
originalContentMap.set(block, block.innerHTML);
}
try {
block.innerHTML = initialPreHTML; // Reset to known state
let codeElement = block.querySelector('code');
let actualCodeOriginalHTML;
if (codeElement) {
actualCodeOriginalHTML = codeElement.innerHTML;
} else {
actualCodeOriginalHTML = block.innerHTML;
codeElement = document.createElement('code');
}
// Ensure codeElement is a child of block
if (!block.contains(codeElement) || !codeElement.parentElement) {
block.innerHTML = '';
block.appendChild(codeElement);
}
const originalHtmlLinesForAnalysis = actualCodeOriginalHTML.split('\n');
const lineDataArray = [];
let overallBlockContainsDiffMeta = false;
// Determine if block contains diff meta
for (const lineHtml of originalHtmlLinesForAnalysis) {
let textLine = lineHtml.replace(/<[^>]*>/g, '');
textLine = textLine.replace(/+/g, '+').replace(/−/g, '-').replace(///g, '/').replace(/ /g, ' ');
if (/^(diff --git |index |--- a\/|^\+\+\+ b\/|@@ )/.test(textLine)) {
overallBlockContainsDiffMeta = true;
break;
}
}
if (!overallBlockContainsDiffMeta) {
// --- NON-DIFF BLOCK PROCESSING ---
const plainTextContentLines = [];
const correspondingOriginalHtmlLines = [];
originalHtmlLinesForAnalysis.forEach(htmlLine => {
const textVersion = htmlLine.replace(/<[^>]*>/g, '').trim();
if (textVersion !== '') {
plainTextContentLines.push(textVersion);
correspondingOriginalHtmlLines.push(htmlLine);
}
});
if (plainTextContentLines.length === 0) {
codeElement.innerHTML = '';
} else {
const fullPlainTextForDetection = plainTextContentLines.join('\n');
const autoResult = hljs.highlightAuto(fullPlainTextForDetection);
let language = autoResult.language || 'plaintext';
let isLikelyFormattedText = !autoResult.language || autoResult.relevance < 5 ||
(language === 'plaintext' && fullPlainTextForDetection.match(/^(\s*[`*\-+]|\s*\[)/m));
if (isLikelyFormattedText) {
const finalOutput = correspondingOriginalHtmlLines
.map(l => `<span class="line" data-line-type="normal">${l}</span>`).join('');
codeElement.innerHTML = finalOutput;
codeElement.className = 'hljs';
} else {
const plainTextForHighlighting = originalHtmlLinesForAnalysis
.map(lineHtml => {
let text = lineHtml.replace(/<[^>]*>/g, '');
text = text.replace(/+/g, '+').replace(/−/g, '-').replace(///g, '/').replace(/ /g, ' ');
return text;
})
.filter(textLine => textLine.trim() !== '')
.join('\n');
const highlightResult = hljs.highlight(plainTextForHighlighting, { language: language, ignoreIllegals: true });
const highlightedOutputLines = highlightResult.value.split('\n');
const finalOutput = highlightedOutputLines
.filter(l => l.trim() !== '')
.map(l => `<span class="line" data-line-type="normal">${l}</span>`).join('');
codeElement.innerHTML = finalOutput;
codeElement.className = `language-${language} hljs`;
}
}
} else {
// --- DIFF BLOCK PROCESSING ---
const plainTextLinesForActualDiffHighlighting = [];
let hasEncounteredAnyDiffMetaLine = false;
let inHunkSection = false;
originalHtmlLinesForAnalysis.forEach((lineHtml) => {
let textLine = lineHtml.replace(/<[^>]*>/g, '');
textLine = textLine.replace(/+/g, '+').replace(/−/g, '-').replace(///g, '/').replace(/ /g, ' ');
const trimmedTextLine = textLine.trim();
let entry = {
originalHtml: lineHtml, type: 'unknown', shouldRender: true,
textContentForHighlight: null
};
if (/^(diff --git |index |--- a\/|^\+\+\+ b\/)/.test(textLine)) {
entry.type = 'meta'; hasEncounteredAnyDiffMetaLine = true; inHunkSection = false;
} else if (/^@@ /.test(textLine)) {
entry.type = 'meta'; hasEncounteredAnyDiffMetaLine = true; inHunkSection = true;
} else if (trimmedTextLine === '') {
entry.type = 'empty_original_in_hunk';
entry.shouldRender = inHunkSection;
if (inHunkSection) {
entry.textContentForHighlight = '';
plainTextLinesForActualDiffHighlighting.push('');
}
} else if (hasEncounteredAnyDiffMetaLine && inHunkSection) {
const diffMatch = textLine.match(/^([ \t]*)([\+\-])( ?)(.*)/);
let contentForHighlight;
if (diffMatch) {
entry.type = diffMatch[2] === '+' ? 'hunk_addition' : 'hunk_deletion';
contentForHighlight = diffMatch[1] + diffMatch[4];
if (diffMatch[4].trim() === '') {
entry.shouldRender = true;
entry.textContentForHighlight = diffMatch[1];
plainTextLinesForActualDiffHighlighting.push(diffMatch[1]);
} else {
const commentLineMatch = contentForHighlight.match(/^(\s*)(\*.*)/);
if (commentLineMatch) {
contentForHighlight = commentLineMatch[2];
}
entry.textContentForHighlight = contentForHighlight;
plainTextLinesForActualDiffHighlighting.push(contentForHighlight);
}
} else {
entry.type = 'hunk_context';
contentForHighlight = textLine;
const commentLineMatch = contentForHighlight.match(/^(\s*)(\*.*)/);
if (commentLineMatch) {
contentForHighlight = commentLineMatch[2];
}
entry.textContentForHighlight = contentForHighlight;
plainTextLinesForActualDiffHighlighting.push(contentForHighlight);
}
} else if (hasEncounteredAnyDiffMetaLine && !inHunkSection) {
entry.type = 'verbatim_inter_hunk';
} else {
entry.type = 'verbatim_preamble';
}
lineDataArray.push(entry);
});
let highlightedHunkContentLines = [];
if (plainTextLinesForActualDiffHighlighting.length > 0) {
const textToHighlight = plainTextLinesForActualDiffHighlighting.join('\n');
const highlightResult = hljs.highlight(textToHighlight, { language: 'c', ignoreIllegals: true });
highlightedHunkContentLines = highlightResult.value.split('\n');
if (plainTextLinesForActualDiffHighlighting.length !== highlightedHunkContentLines.length) {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): [Block ${index}] Diff highlight line count mismatch! Fallback.`); // Keep warning
codeElement.innerHTML = actualCodeOriginalHTML; codeElement.className = 'language-c hljs';
hljs.highlightElement(codeElement);
codeElement.innerHTML = codeElement.innerHTML.split('\n')
.filter(l => l.trim() !== '').map(l => `<span class="line">${l || ''}</span>`).join('');
block.dataset.lwnBeautifierProcessed = 'true';
if (!block.classList.contains('hljs')) block.classList.add('hljs');
if (codeElement && !codeElement.classList.contains('hljs')) codeElement.classList.add('hljs');
return;
}
}
const finalHtmlLines = [];
let highlightIndex = 0;
lineDataArray.forEach(data => {
if (!data.shouldRender) {
return;
}
let lineOutput = '';
switch (data.type) {
case 'meta':
lineOutput = `<span class="line diff-meta-line">${data.originalHtml || ''}</span>`;
break;
case 'hunk_addition':
case 'hunk_deletion':
case 'hunk_context':
case 'empty_original_in_hunk':
const markerText = data.type === 'hunk_addition' ? '+' :
(data.type === 'hunk_deletion' ? '-' : ' ');
const codeContent = data.textContentForHighlight !== null && highlightedHunkContentLines[highlightIndex] !== undefined
? highlightedHunkContentLines[highlightIndex]
: (data.textContentForHighlight || '');
lineOutput = `<span class="line is-diff-hunk-line" data-line-type="${data.type === 'empty_original_in_hunk' ? 'hunk_context' : data.type}">` +
`<span class="diff-marker-column">${markerText}</span>` +
`<span class="diff-code-column">${codeContent}</span>` +
`</span>`;
if (data.textContentForHighlight !== null) {
highlightIndex++;
}
break;
case 'verbatim_preamble':
case 'verbatim_inter_hunk':
lineOutput = `<span class="line" data-line-type="normal">${data.originalHtml || ''}</span>`;
break;
default: // Should not happen with current logic
lineOutput = `<span class="line" data-line-type="normal">${data.originalHtml || ''}</span>`;
}
if (lineOutput) finalHtmlLines.push(lineOutput);
});
codeElement.innerHTML = finalHtmlLines.join('');
}
// Final setup for the processed block
block.dataset.lwnBeautifierProcessed = 'true';
if (!block.classList.contains('hljs')) block.classList.add('hljs');
if (codeElement && !codeElement.classList.contains('hljs')) codeElement.classList.add('hljs');
} catch (e) {
// Keep error log
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): [Block ${index}] Error during highlighting:`, e, block);
console.error(`LWN Code Beautifier (${SCRIPT_VERSION}): [Block ${index}] Error during highlighting:`, e, block);
if (originalContentMap.has(block)) {
block.innerHTML = originalContentMap.get(block); // Attempt restore on error
}
delete block.dataset.lwnBeautifierProcessed;
}
});
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Highlighting finished.`); // Keep end log
}
// --- Restore Original State ---
function restoreOriginal() {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Restoring original state...`); // Keep log
document.body.classList.remove('lwn-beautifier-active');
originalContentMap.forEach((originalPreHTML, block) => {
if (document.body.contains(block)) {
block.innerHTML = originalPreHTML;
block.classList.remove('hljs');
delete block.dataset.lwnBeautifierProcessed;
} else {
originalContentMap.delete(block); // Clean up map if block removed from DOM
}
});
}
// --- Toggle Button ---
function createToggleButton() {
let button = document.getElementById('lwn-beautifier-toggle');
if (button) {
button.textContent = isEnhanced ? 'Restore Original' : 'Enable Beautifier';
return;
}
button = document.createElement('button');
button.id = 'lwn-beautifier-toggle';
button.textContent = isEnhanced ? 'Restore Original' : 'Enable Beautifier';
Object.assign(button.style, {
position: 'fixed', bottom: '15px', right: '15px', zIndex: '9999',
padding: '8px 12px', backgroundColor: '#007bff', color: 'white',
border: 'none', borderRadius: '4px', cursor: 'pointer',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)', fontSize: '13px'
});
button.addEventListener('click', () => {
isEnhanced = !isEnhanced;
GM_setValue('lwnBeautifierEnabled', isEnhanced);
button.textContent = isEnhanced ? 'Restore Original' : 'Enable Beautifier';
if (isEnhanced) {
if (styleElement) styleElement.textContent = getEnhancedCss();
document.body.classList.add('lwn-beautifier-active');
applyHighlighting();
} else {
restoreOriginal();
}
});
document.body.appendChild(button);
}
// --- Initialization ---
function initialize() {
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Initializing...`); // Keep log
styleElement = document.getElementById(enhancedStylesId);
if (!styleElement) {
styleElement = document.createElement('style');
styleElement.id = enhancedStylesId;
document.head.appendChild(styleElement);
}
if (isEnhanced) {
styleElement.textContent = getEnhancedCss();
document.body.classList.add('lwn-beautifier-active');
// Delay slightly to ensure page elements are ready
setTimeout(applyHighlighting, 250);
} else {
styleElement.textContent = '';
document.body.classList.remove('lwn-beautifier-active');
GM_log(`LWN Code Beautifier (${SCRIPT_VERSION}): Beautifier currently disabled.`); // Keep log
}
createToggleButton();
}
// --- Script Entry Point ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize(); // DOM already loaded
}
})();