|  | 
|  | 1 | +//===----------------------------------------------------------------------===// | 
|  | 2 | +// | 
|  | 3 | +// This source file is part of the VSCode Swift open source project | 
|  | 4 | +// | 
|  | 5 | +// Copyright (c) 2021-2024 the VSCode Swift project authors | 
|  | 6 | +// Licensed under Apache License v2.0 | 
|  | 7 | +// | 
|  | 8 | +// See LICENSE.txt for license information | 
|  | 9 | +// See CONTRIBUTORS.txt for the list of VSCode Swift project authors | 
|  | 10 | +// | 
|  | 11 | +// SPDX-License-Identifier: Apache-2.0 | 
|  | 12 | +// | 
|  | 13 | +//===----------------------------------------------------------------------===// | 
|  | 14 | + | 
|  | 15 | +import * as fs from "fs/promises"; | 
|  | 16 | +import * as path from "path"; | 
|  | 17 | +import * as vscode from "vscode"; | 
|  | 18 | +import { tmpdir } from "os"; | 
|  | 19 | +import { exec } from "child_process"; | 
|  | 20 | +import { SwiftOutputChannel } from "../ui/SwiftOutputChannel"; | 
|  | 21 | +import { WorkspaceContext } from "../WorkspaceContext"; | 
|  | 22 | + | 
|  | 23 | +export async function captureDiagnostics(ctx: WorkspaceContext) { | 
|  | 24 | +    const diagnosticsDir = path.join( | 
|  | 25 | +        tmpdir(), | 
|  | 26 | +        `vscode-diagnostics-${formatDateString(new Date())}` | 
|  | 27 | +    ); | 
|  | 28 | + | 
|  | 29 | +    const versionOutputChannel = new SwiftOutputChannel(); | 
|  | 30 | +    ctx.toolchain.logDiagnostics(versionOutputChannel); | 
|  | 31 | + | 
|  | 32 | +    const logs = ctx.outputChannel.logs.join("\n"); | 
|  | 33 | +    const versionLogs = versionOutputChannel.logs.join("\n"); | 
|  | 34 | +    const diagnosticLogs = buildDiagnostics(); | 
|  | 35 | + | 
|  | 36 | +    try { | 
|  | 37 | +        await fs.mkdir(diagnosticsDir); | 
|  | 38 | +        await fs.writeFile(path.join(diagnosticsDir, "logs.txt"), logs); | 
|  | 39 | +        await fs.writeFile(path.join(diagnosticsDir, "version.txt"), versionLogs); | 
|  | 40 | +        await fs.writeFile(path.join(diagnosticsDir, "diagnostics.txt"), diagnosticLogs); | 
|  | 41 | + | 
|  | 42 | +        ctx.outputChannel.log(`Saved diagnostics to ${diagnosticsDir}`); | 
|  | 43 | + | 
|  | 44 | +        const showInFinderButton = "Show In Finder"; | 
|  | 45 | +        const copyPath = "Copy Path to Clipboard"; | 
|  | 46 | +        const infoDialogButtons = [ | 
|  | 47 | +            ...(process.platform === "darwin" ? [showInFinderButton] : []), | 
|  | 48 | +            copyPath, | 
|  | 49 | +        ]; | 
|  | 50 | +        const result = await vscode.window.showInformationMessage( | 
|  | 51 | +            `Saved diagnostic logs to ${diagnosticsDir}`, | 
|  | 52 | +            ...infoDialogButtons | 
|  | 53 | +        ); | 
|  | 54 | +        if (result === copyPath) { | 
|  | 55 | +            vscode.env.clipboard.writeText(diagnosticsDir); | 
|  | 56 | +        } else if (result === showInFinderButton) { | 
|  | 57 | +            exec(`open ${diagnosticsDir}`, error => { | 
|  | 58 | +                if (error) { | 
|  | 59 | +                    vscode.window.showErrorMessage(`Failed to open Finder: ${error.message}`); | 
|  | 60 | +                    return; | 
|  | 61 | +                } | 
|  | 62 | +            }); | 
|  | 63 | +        } | 
|  | 64 | +    } catch (error) { | 
|  | 65 | +        vscode.window.showErrorMessage(`Unable to captrure diagnostics logs: ${error}`); | 
|  | 66 | +    } | 
|  | 67 | +} | 
|  | 68 | + | 
|  | 69 | +function buildDiagnostics(): string { | 
|  | 70 | +    const diagnosticToString = (diagnostic: vscode.Diagnostic) => { | 
|  | 71 | +        return `${severityToString(diagnostic.severity)} - ${diagnostic.message} [Ln ${diagnostic.range.start.line}, Col ${diagnostic.range.start.character}]`; | 
|  | 72 | +    }; | 
|  | 73 | + | 
|  | 74 | +    return vscode.languages | 
|  | 75 | +        .getDiagnostics() | 
|  | 76 | +        .map( | 
|  | 77 | +            ([uri, diagnostics]) => `${uri}\n\t${diagnostics.map(diagnosticToString).join("\n\t")}` | 
|  | 78 | +        ) | 
|  | 79 | +        .join("\n"); | 
|  | 80 | +} | 
|  | 81 | + | 
|  | 82 | +function severityToString(severity: vscode.DiagnosticSeverity): string { | 
|  | 83 | +    switch (severity) { | 
|  | 84 | +        case vscode.DiagnosticSeverity.Error: | 
|  | 85 | +            return "Error"; | 
|  | 86 | +        case vscode.DiagnosticSeverity.Warning: | 
|  | 87 | +            return "Warning"; | 
|  | 88 | +        case vscode.DiagnosticSeverity.Information: | 
|  | 89 | +            return "Information"; | 
|  | 90 | +        case vscode.DiagnosticSeverity.Hint: | 
|  | 91 | +            return "Hint"; | 
|  | 92 | +    } | 
|  | 93 | +} | 
|  | 94 | + | 
|  | 95 | +function padZero(num: number, length: number = 2): string { | 
|  | 96 | +    return num.toString().padStart(length, "0"); | 
|  | 97 | +} | 
|  | 98 | + | 
|  | 99 | +function formatDateString(date: Date): string { | 
|  | 100 | +    const year = date.getFullYear(); | 
|  | 101 | +    const month = padZero(date.getMonth() + 1); | 
|  | 102 | +    const day = padZero(date.getDate()); | 
|  | 103 | +    const hours = padZero(date.getHours()); | 
|  | 104 | +    const minutes = padZero(date.getMinutes()); | 
|  | 105 | +    const seconds = padZero(date.getSeconds()); | 
|  | 106 | +    const timezoneOffset = -date.getTimezoneOffset(); | 
|  | 107 | +    const timezoneSign = timezoneOffset >= 0 ? "+" : "-"; | 
|  | 108 | +    const timezoneHours = padZero(Math.floor(Math.abs(timezoneOffset) / 60)); | 
|  | 109 | +    const timezoneMinutes = padZero(Math.abs(timezoneOffset) % 60); | 
|  | 110 | +    return `${year}-${month}-${day}T${hours}-${minutes}-${seconds}${timezoneSign}${timezoneHours}-${timezoneMinutes}`; | 
|  | 111 | +} | 
0 commit comments