import * as vscode from 'vscode'; import * as fs from 'fs'; import * as fspath from 'path'; import { AbsPath, opeParam, MainOutput, ReportType } from '../../global'; import { hdlParam, HdlModule, HdlFile, HdlInstance } from '../../hdlParser/core'; import { HdlModulePort, HdlModuleParam, InstModPathStatus } from '../../hdlParser/common'; import { MarkdownString, RenderString, RenderType, mergeSortByLine, getWavedromsFromFile, Count, WavedromString } from './common'; import { hdlPath, hdlFile } from '../../hdlFs'; import { getSymbolComments } from '../lsp/util/feature'; import { HdlLangID, ThemeType } from '../../global/enum'; import { makeDiagram } from './diagram'; import { defaultMacro, doFastApi } from '../../hdlParser/util'; import { t } from '../../i18n'; function makeSVGElementByLink(link: AbsPath, caption?: string) { let mainHtml; if (caption) { mainHtml = `

${caption}

`; } else { mainHtml = `
`; } return '
' + mainHtml + '

\n'; } function selectFieldValue(obj: any, subName: string, ws: string, name: string, isSingleFile: boolean): string { if (subName === 'empty') { return '——'; } let value = obj[subName]; // 对于 source ,支持跳转 if (subName === 'instModPath') { // 如果是单文件,则直接返回 —— if (isSingleFile) { return '——'; } const relativePath = value.replace(ws, ''); if (fs.existsSync(value)) { // 判断 类型 const hdlFile = hdlParam.getHdlFile(value); if (hdlFile && hdlFile.type === 'remote_lib') { // 如果是 库 文件,做出更加自定义的字面量 const libRelPath = value.replace(`${opeParam.extensionPath}/library/`, ''); value = `library [${libRelPath}](file://${value})`; } else { value = `project [${relativePath}](file://${value})`; } } else { value = 'unknown ' + t('info.dide-doc.source.cannot-find'); } } if (value && value.trim().length === 0) { return '——'; } // TODO : 1 not known if (name === 'ports' && value === '1') { return '——'; } return value; } function makeTableFromObjArray( md: MarkdownString, array: any[], name: string, fieldNames: string[], displayNames: string[], isSingleFile: boolean ) { const ws = hdlPath.toSlash(opeParam.workspacePath) + '/'; const rows = []; for (const obj of array) { const data = []; for (const subName of fieldNames) { const value = selectFieldValue(obj, subName, ws, name, isSingleFile); data.push(value); } rows.push(data); } if (displayNames) { md.addTable(displayNames, rows); } else { md.addTable(fieldNames, rows); } } /** * @description add attribute description to each port/param * @param {string} path * @param {Array} ports */ async function patchComment(path: AbsPath, ports: (HdlModulePort | HdlModuleParam)[]) { if (!ports || !ports.length) { return; } const ranges = ports.map(port => port.range); const comments = await getSymbolComments(path, ranges); for (let i = 0; i < ports.length; ++ i) { let inlineComment = comments[i] .replace(/\n\n/g, '
') .replace(/\n/g, '
') if (inlineComment.startsWith('//') || inlineComment.startsWith('--')) { inlineComment = inlineComment.substring(2); } ports[i].desc = inlineComment; } } /** * @description get basedoc obj from a module * @param module */ async function getDocsFromModule(module: HdlModule): Promise { const moduleName = module.name; const portNum = module.ports.length; const paramNum = module.params.length; // add desc can optimizer in the future version const paramPP = patchComment(module.path, module.params); const portPP = patchComment(module.path, module.ports); const md = new MarkdownString(module.range.start.line); if (module.languageId === HdlLangID.Vhdl) { md.addTitle(moduleName + `VHDL ${t('info.dide-doc.entity')}`, 1); } else if (module.languageId === HdlLangID.Verilog) { md.addTitle(moduleName + `Verilog ${t('info.dide-doc.module')}`, 1); } else if (module.languageId === HdlLangID.SystemVerilog) { md.addTitle(moduleName + `SystemVerilog ${t('info.dide-doc.module')}`, 1); } // add module name // md.addTitle(t('info.hdl-doc.markdown.basic-info'), 2); const infos = [ '' + ' ' + fspath.basename(module.file.path), '' + ' ' + `${paramNum}`, '' + ' ' + `${portNum}` ]; if (hdlParam.isTopModule(module.path, module.name)) { infos.push('' + ' ' + '√'); } else { infos.push('' + ' ' + '×'); } // md.addUnorderedList(infos); md.addText(infos.join(" ")); md.addEnter(); const diagram = makeDiagram(module.params, module.ports); md.addText(diagram); // wait param and port patch await paramPP; await portPP; // 判断是否为单文件 let isSingleFile = false; if (!opeParam.workspacePath || !fs.existsSync(opeParam.workspacePath)) { isSingleFile = true; } else { const workspacePath = opeParam.workspacePath; const modulePath = module.path; isSingleFile = !modulePath.startsWith(workspacePath); } // param section const paramTitleIcon = ' '; md.addTitle(paramTitleIcon + t('info.dide-doc.parameters'), 2); if (module.params.length > 0) { makeTableFromObjArray(md, module.params, 'params', ['name', 'init', 'empty', 'desc'], // 'Param Name', 'Init', 'Range', 'Description' [ t('info.dide-doc.param-name'), t('info.dide-doc.parameter-init'), t('info.dide-doc.range'), t('info.dide-doc.description') ], isSingleFile); } else { md.addText(t('info.dide-doc.no-parameter-info')); } md.addEnter(); md.addEnter(); // port section const portTitleIcon = ' '; md.addTitle(portTitleIcon + t('info.dide-doc.ports'), 2); if (module.ports.length > 0) { makeTableFromObjArray(md, module.ports, 'ports', ['name', 'type', 'width', 'desc'], // 'Port Name', 'Direction', 'Range', 'Description' [ t('info.dide-doc.port-name'), t('info.dide-doc.direction'), t('info.dide-doc.range'), t('info.dide-doc.description') ], isSingleFile); } else { md.addText(t('info.dide-doc.no-port-info')); } md.addEnter(); md.addEnter(); // dependency section const depTitleIcon = ' '; md.addTitle(depTitleIcon + t('info.dide-doc.dependency'), 2); let insts = module.getAllInstances(); // 对于单文件模式而言,未进行 instance 搜索,所以insts必然是空的 if (isSingleFile && insts.length === 0 && module.rawInstances) { insts = module.rawInstances.map(rawInstance => new HdlInstance( rawInstance.name, rawInstance.type, undefined, InstModPathStatus.Unknown, rawInstance.instparams, rawInstance.instports, rawInstance.range, module )); } // 根据 start 进行排序 insts.sort((a, b) => a.range.start.line - b.range.start.line); if (insts.length > 0) { makeTableFromObjArray(md, insts, 'Dependencies', ['name', 'type', 'instModPath'], // 'name', 'module', 'source' [ t('info.dide-doc.module-name'), t('info.dide-doc.module'), t("info.dide-doc.source") ], isSingleFile); } else { md.addText(t('info.dide-doc.no-dep-info')); } md.addEnter(); md.addEnter(); return md; } /** * @description get basedoc obj according to a file * @param path absolute path of the file */ async function getDocsFromFile(path: AbsPath): Promise { let moduleFile = hdlParam.getHdlFile(path); // 没有说明是单文件模式,直接打开解析 if (!moduleFile) { const standardPath = hdlPath.toSlash(path); const response = await doFastApi(standardPath, 'common'); const langID = hdlFile.getLanguageId(standardPath); moduleFile = new HdlFile( standardPath, langID, response?.macro || defaultMacro, response?.content || [], 'common' ); // 从 hdlParam 中去除,避免干扰全局 hdlParam.removeFromHdlFile(moduleFile); // const message = t('error.common.not-valid-hdl-file'); // const errorMsg = path + ' ' + message + ' ' + opeParam.prjInfo.hardwareSrcPath + '\n' + opeParam.prjInfo.hardwareSimPath; // vscode.window.showErrorMessage(errorMsg); // return undefined; } const markdownStringPromises = []; for (const module of moduleFile.getAllHdlModules()) { const markdownStringPromise = getDocsFromModule(module); markdownStringPromises.push(markdownStringPromise); } const fileDocs = []; for (const p of markdownStringPromises) { const markdownString = await p; fileDocs.push(markdownString); } return fileDocs; } /** * @description get render list of path * @param path */ async function getRenderList(path: AbsPath): Promise { if (!hdlFile.isHDLFile(path)) { vscode.window.showErrorMessage('Please use the command in a HDL file!'); return []; } const docs = await getDocsFromFile(path); const svgs = await getWavedromsFromFile(path); if (docs && svgs) { const renderList = mergeSortByLine(docs, svgs); return renderList; } return undefined; } /** * @description return render list of current file */ async function getCurrentRenderList(uri: vscode.Uri): Promise { const currentFilePath = hdlPath.toSlash(uri.fsPath); return await getRenderList(currentFilePath); } async function exportCurrentFileDocAsMarkdown() { const editor = vscode.window.activeTextEditor; if (!editor) { return; } const currentFilePath = hdlPath.toSlash(editor.document.fileName); const hdlFileName = hdlPath.basename(currentFilePath); const renderList = await getRenderList(currentFilePath); if (!renderList || renderList.length === 0) { return; } const wsPath = opeParam.workspacePath; const markdownFolderPath = hdlPath.join(wsPath, 'markdown'); if (!fs.existsSync(markdownFolderPath)) { fs.mkdirSync(markdownFolderPath); } const currentRoot = hdlPath.join(markdownFolderPath, hdlFileName); if (fs.existsSync(currentRoot)) { hdlFile.rmSync(currentRoot); } fs.mkdirSync(currentRoot); const figureFolder = hdlPath.join(currentRoot, 'figure'); fs.mkdirSync(figureFolder); let markdown = ''; for (const r of renderList) { if (r instanceof MarkdownString) { markdown += r.renderMarkdown() + '\n'; } else if (r instanceof WavedromString) { const svgString = r.render(); const svgName = 'wavedrom-' + Count.svgMakeTimes + '.svg'; const svgPath = hdlPath.join(figureFolder, svgName); fs.writeFileSync(svgPath, svgString); const relatePath = hdlPath.join('./figure', svgName); const svgHtml = makeSVGElementByLink(relatePath); markdown += '\n\n' + svgHtml + '\n\n'; } } const markdownName = 'index.md'; const markdownPath = hdlPath.join(currentRoot, markdownName); Count.svgMakeTimes = 0; fs.writeFileSync(markdownPath, markdown); } async function exportProjectDocAsMarkdown() { vscode.window.showInformationMessage('this is exportProjectDocAsMarkdown'); } export { getDocsFromFile, getRenderList, getCurrentRenderList, exportCurrentFileDocAsMarkdown, exportProjectDocAsMarkdown };