import { LanguageClient, LanguageClientOptions, ServerOptions, Executable, } from "vscode-languageclient/node"; import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import * as zlib from 'zlib'; import * as tar from 'tar'; import { platform } from "os"; import { IProgress, LspClient, opeParam } from '../../global'; import axios, { AxiosResponse } from "axios"; import { chooseBestDownloadSource } from "./cdn"; import { hdlDir, hdlPath } from "../../hdlFs"; import { registerConfigurationUpdater, registerLinter } from "./config"; import { t } from "../../i18n"; import { getPlatformPlatformSignature } from "../../global/util"; function getLspServerExecutionName() { const osname = platform(); if (osname === 'win32') { return 'digital-lsp.exe'; } return 'digital-lsp'; } function extractTarGz(filePath: string, outputDir: string): Promise { if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const inputStream = fs.createReadStream(filePath); const gunzip = zlib.createGunzip(); const extract = tar.extract({ cwd: outputDir, // 解压到指定目录 }); inputStream.pipe(gunzip).pipe(extract as NodeJS.WritableStream); return new Promise((resolve, reject) => { extract.on('finish', () => { for (const file of fs.readdirSync(outputDir)) { const filePath = hdlPath.join(outputDir, file); fs.chmodSync(filePath, '755'); } resolve(); }); extract.on('error', reject); }) } function streamDownload(context: vscode.ExtensionContext, progress: vscode.Progress, response: AxiosResponse): Promise { const totalLength = response.headers['content-length']; const totalSize = parseInt(totalLength); let downloadSize = 0; const savePath = context.asAbsolutePath( path.join('resources', 'dide-lsp', 'server', 'tmp.tar.gz') ); const fileStream = fs.createWriteStream(savePath); response.data.pipe(fileStream); return new Promise((resolve, reject) => { response.data.on('data', (chunk: Buffer) => { downloadSize += chunk.length; let precent = Math.ceil(downloadSize / totalSize * 100); let increment = chunk.length / totalSize * 100; progress.report({ message: `${precent}%`, increment }); }); fileStream.on('finish', () => { console.log('finish download'); fileStream.close(); resolve(savePath); }); fileStream.on('error', reject); }); } async function checkAndDownload(context: vscode.ExtensionContext, version: string): Promise { const versionFolderPath = context.asAbsolutePath( path.join('resources', 'dide-lsp', 'server', version) ); const serverPath = path.join(versionFolderPath, getLspServerExecutionName()); if (fs.existsSync(versionFolderPath) && fs.existsSync(serverPath)) { return true; } const serverFolder = context.asAbsolutePath( path.join('resources', 'dide-lsp', 'server') ); hdlDir.mkdir(serverFolder); return await downloadLsp(context, version, versionFolderPath); } export async function downloadLsp(context: vscode.ExtensionContext, version: string, versionFolderPath: string): Promise { const downloadLink = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t('info.progress.choose-best-download-source') }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { let timeout = 3000; let reportInterval = 500; const intervalHandler = setInterval(() => { progress.report({ increment: reportInterval / timeout * 100 }); }, reportInterval); const signature = getPlatformPlatformSignature().toString(); const downloadLink = await chooseBestDownloadSource(signature, version, timeout); console.log('choose download link: ' + downloadLink); clearInterval(intervalHandler); return downloadLink }); const tarGzFilePath = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t('info.progress.download-digital-lsp') }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { progress.report({ increment: 0 }); const response = await axios.get(downloadLink, { responseType: 'stream' }); return await streamDownload(context, progress, response); }); await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: t('info.progress.extract-digital-lsp') }, async (progress: vscode.Progress, token: vscode.CancellationToken) => { if (fs.existsSync(tarGzFilePath)) { console.log('check finish, begin to extract'); await extractTarGz(tarGzFilePath, versionFolderPath); } else { vscode.window.showErrorMessage(t('error.lsp.download-digital-lsp') + version); } }); return false; } export async function activate(context: vscode.ExtensionContext, packageJson: any) { const version = packageJson.version; await checkAndDownload(context, version); const lspServerName = getLspServerExecutionName(); const lspServerPath = context.asAbsolutePath( path.join('resources', 'dide-lsp', 'server', version, lspServerName) ); if (fs.existsSync(lspServerPath)) { console.log('lsp server found at ' + lspServerPath); } else { console.error('cannot found lsp server at ' + lspServerPath); } const run: Executable = { command: lspServerPath, }; // If the extension is launched in debug mode then the debug server options are used // Otherwise the run options are used let serverOptions: ServerOptions = { run, debug: run, }; let workspaceFolder: undefined | { uri: vscode.Uri, name: string, index: number } = undefined; if (vscode.workspace.workspaceFolders) { const currentWsFolder = vscode.workspace.workspaceFolders[0]; workspaceFolder = currentWsFolder; } let extensionPath = hdlPath.toSlash(context.extensionPath); let clientOptions: LanguageClientOptions = { documentSelector: [ { scheme: 'file', language: 'systemverilog' }, { scheme: 'file', language: 'verilog' }, { scheme: 'file', language: 'vhdl' } ], progressOnInitialization: true, markdown: { isTrusted: true, supportHtml: true }, workspaceFolder, initializationOptions: { extensionPath, toolChain: opeParam.prjInfo.toolChain, version: version } }; const client = new LanguageClient( "Digital LSP", "Digital LSP", serverOptions, clientOptions ); LspClient.DigitalIDE = client; // 启动 lsp await client.start(); // 检测配置文件变动 await registerConfigurationUpdater(client, packageJson); // 配置诊断器 await registerLinter(client); } export function deactivate(): Thenable | undefined { if (!LspClient.DigitalIDE) { return undefined; } return LspClient.DigitalIDE.stop(); }