import * as vscode from 'vscode'; import { ScanIssue } from '../scanner/prompt'; export class ResultsPanel { private static _current: ResultsPanel | undefined; private readonly _panel: vscode.WebviewPanel; private _disposables: vscode.Disposable[] = []; static show(context: vscode.ExtensionContext, issues: ScanIssue[], source: string): void { if (ResultsPanel._current) { ResultsPanel._current._panel.reveal(vscode.ViewColumn.Beside); ResultsPanel._current._update(issues, source); return; } ResultsPanel._current = new ResultsPanel(context, issues, source); } private constructor( context: vscode.ExtensionContext, issues: ScanIssue[], source: string, ) { this._panel = vscode.window.createWebviewPanel( 'guardResults', 'Guard スキャン結果', vscode.ViewColumn.Beside, { enableScripts: false, retainContextWhenHidden: true }, ); this._panel.onDidDispose(() => { ResultsPanel._current = undefined; this._disposables.forEach(d => d.dispose()); }, null, this._disposables); this._update(issues, source); } private _update(issues: ScanIssue[], source: string): void { this._panel.title = `Guard: ${source}`; this._panel.webview.html = buildHtml(issues, source); } } function esc(s: string): string { return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function buildHtml(issues: ScanIssue[], source: string): string { const danger = issues.filter(i => i.severity === 'danger'); const warning = issues.filter(i => i.severity === 'warning'); const info = issues.filter(i => i.severity === 'info'); const badge = (label: string, count: number, color: string) => count > 0 ? `${label} ${count}` : ''; const issueHtml = (issue: ScanIssue) => { const color = issue.severity === 'danger' ? '#e53e3e' : issue.severity === 'warning' ? '#d69e2e' : '#3182ce'; const bg = issue.severity === 'danger' ? '#fff5f5' : issue.severity === 'warning' ? '#fffff0' : '#ebf8ff'; const fixSection = issue.fix ? `
${esc(issue.fix)}
` : ''; const location = issue.line != null ? `${esc(issue.file)}:${issue.line}` : esc(issue.file); return `
${esc(issue.severity)} ${esc(issue.title)}
${esc(issue.description)}
${location}
${fixSection}
`; }; const allHtml = [...danger, ...warning, ...info].map(issueHtml).join(''); return `

Guard スキャン結果

${esc(source)} — ${issues.length} 件の指摘
${badge('危険', danger.length, '#e53e3e')} ${badge('警告', warning.length, '#d69e2e')} ${badge('情報', info.length, '#3182ce')}
${issues.length === 0 ? '
問題は検出されませんでした
' : allHtml } `; }