1/* 2 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// The module 'vscode' contains the VS Code extensibility API 17// Import the module and reference it with the alias vscode in your code below 18const vscode = require('vscode'); 19const fs = require('fs'); 20const re = require('./gen/tools/VsPluginRe'); 21const { VsPluginLog } = require('./gen/tools/VsPluginLog'); 22const { detectPlatform, readFile } = require('./gen/tools/VsPluginTool'); 23const path = require('path'); 24const INVALID_INDEX = -1; 25const SELECT_H_FILE = 2; // 选择.h文件 26let exeFilePath = null; 27let globalPanel = null; 28let showInfoPanel = null; 29let importToolChain = false; 30let extensionIds = []; 31let nextPluginId = null; 32let configList = new Array(); 33let generateDir = ''; 34let cfgPath = ''; 35 36// this method is called when your extension is activated 37// your extension is activated the very first time the command is executed 38 39/** 40 * @param {vscode.ExtensionContext} context 41 */ 42function activate(context) { 43 // Use the console to output diagnostic information (console.log) and errors (console.error) 44 // This line of code will only be executed once when your extension is activated 45 console.log('Congratulations, your extension "napi-gen" is now active!'); 46 let disposable = register(context, 'generate_napi'); 47 let disposableMenu = register(context, 'generate_napi_menu'); 48 context.subscriptions.push(disposable); 49 context.subscriptions.push(disposableMenu); 50 let platform = detectPlatform(); 51 if (platform === 'win') { 52 exeFilePath = __dirname + '/napi_generator-win.exe'; 53 } else if (platform === 'mac') { 54 exeFilePath = __dirname + '/napi_generator-macos'; 55 } else if (platform === 'Linux') { 56 exeFilePath = __dirname + '/napi_generator-linux'; 57 } 58} 59 60function executor(name, genDir, mode, numberType, importIsCheck) { 61 let exec = require('child_process').exec; 62 exec(genCommand(name, genDir, mode, numberType, importIsCheck), function (error, stdout, stderr) { 63 VsPluginLog.logInfo('VsPlugin: stdout =' + stdout + ', stderr =' + stderr); 64 if (error || stdout.indexOf('success') < 0) { 65 vscode.window.showErrorMessage('genError:' + ((error !== null && error !== undefined) ? 66 error : '') + stdout); 67 return VsPluginLog.logError('VsPlugin:' + error + stdout); 68 } 69 return vscode.window.showInformationMessage('Generated successfully'); 70 }); 71} 72 73function genCommand(name, genDir, mode, numberType, importIsCheck) { 74 let genFileMode = mode === 0 ? ' -f ' : ' -d '; 75 76 let genServiceCode = ''; 77 console.log('cfgPath: ' + cfgPath); 78 // -s 判断是否存在文件 ,存在则加-s参数 79 if (fs.existsSync(cfgPath)) { 80 genServiceCode = ' -s ' + cfgPath; 81 console.log('genServiceCode: ' + genServiceCode); 82 } 83 84 if (genDir === '') { 85 return exeFilePath + genFileMode + name + genServiceCode; 86 } 87 return exeFilePath + genFileMode + name + ' -o ' + genDir + ' -n ' + numberType + ' -i ' + importIsCheck + genServiceCode; 88} 89 90function exeFileExit() { 91 if (fs.existsSync(exeFilePath)) { 92 return true; 93 } 94 return false; 95} 96 97function register(context, command) { 98 let disposable = vscode.commands.registerCommand(command, function (uri, boolValue, items) { 99 // The code you place here will be executed every time your command is executed 100 // Display a message box to the user 101 globalPanel = vscode.window.createWebviewPanel( 102 'generate', // Identifies the type of WebView 103 'Generate Napi Frame', // Title of the panel displayed to the user 104 vscode.ViewColumn.Two, // Display the WebView panel in the form of new columns in the editor 105 { 106 enableScripts: true, // Enable or disable JS, default is Enable 107 retainContextWhenHidden: true, // Keep the WebView state when it is hidden to avoid being reset 108 } 109 ); 110 111 checkBoolval(boolValue, items); 112 globalPanel.webview.html = getWebviewContent(context, importToolChain); 113 let msg; 114 globalPanel.webview.onDidReceiveMessage(message => { 115 msg = handleMsg(msg, message, context); 116 }, undefined, context.subscriptions); 117 // 路径有效性判断 118 if (uri.fsPath !== undefined) { 119 let fn = re.getFileInPath(uri.fsPath); 120 let tt = re.match('((@ohos\.)*[a-zA-Z_0-9]+.d.ts)', fn); 121 let result = { 122 msg: 'selectInterPath', 123 path: tt ? uri.fsPath : '' 124 }; 125 globalPanel.webview.postMessage(result); 126 } 127 }); 128 return disposable; 129} 130 131function handleMsg(msg, message, context) { 132 msg = message.msg; 133 if (msg === 'cancel') { 134 globalPanel.dispose(); 135 } else if (msg === 'param') { 136 if (configList.length !== 0) { 137 writeCfgJson(); // 写cfg.json文件 138 } 139 checkReceiveMsg(message); 140 } else if (msg === 'config') { 141 // 若选择文件夹或者选择了多个文件则不能配置业务代码 142 getMsgCfg(message, context); 143 } else { 144 selectPath(globalPanel, message); 145 } 146 return msg; 147} 148function getMsgCfg(message, context) { 149 if (message.mode !== 0 || message.interFile.indexOf(',') > 0) { 150 vscode.window.showInformationMessage('选择文件夹或者多个文件时不能配置业务代码,请选择单个文件'); 151 console.error('选择文件夹或者多个文件时不能配置业务代码,请选择单个文件'); 152 } else if (trimAll(message.genDir).length <= 0) { 153 vscode.window.showInformationMessage('请输入生成框架路径!'); 154 console.error('请输入生成框架路径!'); 155 } else { 156 configServiceCode(message, context); 157 } 158} 159 160function checkBoolval(boolValue, items) { 161 if (typeof (boolValue) === 'boolean' && Array.isArray(items)) { 162 if (boolValue === true) { 163 //遍历数组item,查看当前插件id是数组的第几个元素,并拿出下一个元素,并判断当前id是否是最后一个元素并做相应处理 164 getNextPlugin(items, boolValue); 165 } 166 } 167} 168 169function getNextPlugin(items, boolValue) { 170 let myExtensionId = 'kaihong.napi-gen'; 171 for (let i = 0; i < items.length; i++) { 172 if (myExtensionId === items[i] && (i === items.length - 1)) { 173 importToolChain = false; 174 } else if (myExtensionId === items[i] && (i !== items.length - 1)) { 175 importToolChain = boolValue; 176 nextPluginId = items[i + 1]; 177 } 178 extensionIds.push(items[i]); 179 } 180} 181 182// 去除字符串空格 183function trimAll(str) { 184 return str.split(/[\t\r\f\n\s]*/g).join(''); 185} 186 187function configServiceCode(message, context) { 188 generateDir = message.genDir; 189 // 创建新的面板显示config信息 190 showInfoPanel = vscode.window.createWebviewPanel( 191 'configWebview', 192 'Config', 193 vscode.ViewColumn.Two, 194 { 195 enableScripts: true, 196 retainContextWhenHidden: true, 197 } 198 ); 199 // 加载信息显示页面 200 showInfoPanel.webview.html = getCommonWebViewContent(context, '/vs_showInfo_view.html'); 201 // 处理来自showInfo页面的信息 202 showInfoPanel.webview.onDidReceiveMessage((showInfoMessage) => { 203 handleConfigMsg(showInfoMessage, context); 204 }, undefined, context.subscriptions); 205} 206 207function handleConfigMsg(showInfoMessage, context) { 208 if (showInfoMessage.msg === 'configInfo') { 209 if ((showInfoMessage.index !== INVALID_INDEX && showInfoMessage.type === 'update') || showInfoMessage.type === 'add') { 210 // 配置界面 211 showInfoPanel.webview.html = getCommonWebViewContent(context, '/vs_config_view.html'); 212 showInfoPanel.webview.postMessage({ 213 msg: 'updateData', 214 type: showInfoMessage.type, 215 index: showInfoMessage.index, 216 updateInfo: showInfoMessage.updateInfo, 217 genPath: generateDir, 218 }); 219 } else { 220 console.error('please choose one item to update!'); 221 } 222 } else if (showInfoMessage.msg === 'saveData') { 223 // 判断数据有效性 224 if (validate(showInfoMessage.cfgInfo)) { 225 // 处理来自 vs_config_view.html 的数据保存操作 226 saveData(showInfoMessage.cfgInfo, showInfoMessage.type, showInfoMessage.index); 227 // 返回 vs_showInfo_view.html 作为弹出窗口 228 showInfoPanel.webview.html = getCommonWebViewContent(context, '/vs_showInfo_view.html'); 229 showInfoPanel.webview.postMessage({ 230 msg: 'initShowInfo', 231 configList: getArrayListData(), 232 }); 233 } else { 234 vscode.window.showInformationMessage('配置的信息不能为空!'); 235 console.error('配置的信息不能为空!'); 236 } 237 } else if (showInfoMessage.msg === 'deleteData') { 238 deleteData(showInfoMessage.index); 239 showInfoPanel.webview.html = getCommonWebViewContent(context, '/vs_showInfo_view.html'); 240 showInfoPanel.webview.postMessage({ 241 msg: 'deleteData', 242 configList: getArrayListData(), 243 }); 244 } else if (showInfoMessage.msg === 'cancelShowInfo') { 245 showInfoPanel.dispose(); 246 } else if (showInfoMessage.msg === 'selectIncludeName' || showInfoMessage.msg === 'selectCppName') { 247 selectConfigPath(showInfoPanel, showInfoMessage, generateDir); 248 } else { 249 console.log('showInfoMessage.msg = ' + showInfoMessage.msg); 250 } 251} 252 253// 数据是否为空 254function validate(data) { 255 let includeName = data.includeName; 256 let cppName = data.cppName; 257 let interfaceName = data.interfaceName; 258 let serviceCode = data.serviceCode; 259 if (!isEmptyStr(includeName) && !isEmptyStr(cppName) && !isEmptyStr(interfaceName) && !isEmptyStr(serviceCode)) { 260 return true; 261 } else { 262 return false; 263 } 264} 265 266function isEmptyStr(str) { 267 if (str.length !== 0) { 268 return false; 269 } else { 270 return true; 271 } 272} 273 274function checkReceiveMsg(message) { 275 let mode = message.mode; 276 let name = message.fileNames; 277 let genDir = message.genDir; 278 let numberType = message.numberType; 279 let importIsCheck = message.importIsCheck; 280 let buttonName = message.buttonName; 281 checkMode(name, genDir, mode, numberType, importIsCheck); 282 if (buttonName === 'Next') { 283 startNextPlugin(); 284 } 285} 286 287/** 288 * 将configList写入cfg.json文件 289 */ 290function writeCfgJson() { 291 let platform = detectPlatform(); 292 let json = JSON.stringify(configList); 293 // 处理不同平台配置业务代码时换行符问题 294 if (platform === 'win') { 295 console.log('windows'); 296 json = json.replace(/\\\\r\\\\n/g, '\\n'); 297 json = json.replace(/\\\\n/g, '\\n'); 298 } else if (platform === 'Linux') { 299 json = json.replace(/\\\\n/g, '\\n'); 300 } else if (platform === 'mac') { 301 json = json.replace(/\\\\r/g, '\\r'); 302 } 303 304 // 将JSON字符串写入文件 305 if (generateDir !== '') { 306 cfgPath = generateDir + '/cfg.json'; 307 try { 308 // file written successfully 309 fs.writeFileSync(cfgPath, json.toString()); 310 } catch (err) { 311 console.error(err); 312 } 313 } 314} 315 316/** 317 * 获取configList 318 */ 319function getArrayListData() { 320 return configList; 321} 322 323/** 324 * 增加或者修改一项数据 325 */ 326function saveData(cfgInfo, type, index) { 327 if (type === 'add') { 328 configList.push(cfgInfo); 329 } else if (type === 'update') { 330 configList[index] = cfgInfo; 331 } else { 332 console.error('type error, type is ' + type); 333 } 334} 335 336/** 337 * 删除一项数据 338 */ 339function deleteData(index) { 340 if (index !== -1) { 341 configList.splice(index, 1); 342 } else { 343 console.error('please choose one item to delete!'); 344 } 345} 346 347/** 348 * 获取插件执行命令 349 */ 350function nextPluginExeCommand(nextPluginId) { 351 if (nextPluginId === 'kaihong.ApiScan') { 352 return 'api_scan'; 353 } else if (nextPluginId === 'kaihong.gn-gen') { 354 return 'generate_gn'; 355 } else if (nextPluginId === 'kaihong.service-gen') { 356 return 'generate_service'; 357 } else if (nextPluginId === 'kaihong.ts-gen') { 358 return 'generate_ts'; 359 } else if (nextPluginId === 'kaihong.napi-gen') { 360 return 'generate_napi'; 361 } else { 362 return null; 363 } 364} 365 366/** 367 * 执行完毕后开启工具链中下一个插件 368 */ 369function startNextPlugin() { 370 const extension = vscode.extensions.getExtension(nextPluginId); 371 if (extension) { 372 let startNextPlugin = nextPluginExeCommand(nextPluginId); 373 try { 374 vscode.commands.executeCommand(startNextPlugin, '', importToolChain, extensionIds); 375 } catch (error) { 376 console.error(error); 377 } 378 } 379} 380 381/** 382 * 选择本地 .h/.cpp 文件 383 */ 384function selectConfigPath(panel, message, generateDir) { 385 let mode = SELECT_H_FILE; // 选择.h文件 386 if (message.mode !== undefined || message.mode !== null) { 387 mode = message.mode; 388 } 389 const options = { 390 canSelectMany: false, //是否可以选择多个 391 openLabel: '选择文件', //打开选择的右下角按钮label 392 canSelectFiles: true, //是否选择文件 393 canSelectFolders: false, //是否选择文件夹 394 defaultUri: vscode.Uri.file(''), //默认打开本地路径 395 // 文件过滤选项,在文件夹选择模式下不可设置此配置,否则ubuntu系统下无法选择文件夹 396 filters: mode === SELECT_H_FILE ? { 'Text files': ['h', 'hpp', 'hxx'] } : { 'Text files': ['cpp', 'cc', 'C', 'cxx', 'c++'] }, 397 }; 398 399 return vscode.window.showOpenDialog(options).then(fileUri => { 400 if (fileUri && fileUri[0]) { 401 console.log('Selected file: ' + fileUri[0].fsPath); 402 let fileObsPath = fileUri[0].fsPath; 403 // 获取相对路径 相对于生成框架路径 404 let filePath = path.relative(generateDir, fileObsPath); 405 console.log('relative filePath: ' + filePath); 406 let result = { 407 msg: message.msg, 408 path: filePath, 409 }; 410 panel.webview.postMessage(result); 411 return fileUri[0].fsPath; 412 } 413 return ''; 414 }); 415} 416 417/** 418 * 选择本地目录/文件夹 419 */ 420function selectPath(panel, message) { 421 let mode = 1; 422 if (message.mode !== undefined && message.mode !== null) { 423 mode = message.mode; 424 } 425 const options = { 426 canSelectMany: mode === 0 ? true : false, //是否可以选择多个 427 openLabel: mode === 0 ? '选择文件' : '选择文件夹', //打开选择的右下角按钮label 428 canSelectFiles: mode === 0 ? true : false, //是否选择文件 429 canSelectFolders: mode === 0 ? false : true, //是否选择文件夹 430 defaultUri: vscode.Uri.file(''), //默认打开本地路径 431 // 文件过滤选项,在文件夹选择模式下不可设置此配置,否则ubuntu系统下无法选择文件夹 432 filters: mode === 1 ? {} : { 'Text files': ['d.ts'] }, 433 }; 434 435 return vscode.window.showOpenDialog(options).then((fileUri) => { 436 if (fileUri && fileUri[0]) { 437 console.log('Selected file: ' + fileUri[0].fsPath); 438 let filePath = ''; 439 for (let index = 0; index < fileUri.length; index++) { 440 filePath += fileUri[index].fsPath.concat(','); 441 } 442 let result = { 443 msg: message.msg, 444 path: filePath.length > 0 ? filePath.substring(0, filePath.length - 1) : filePath, 445 }; 446 panel.webview.postMessage(result); 447 return fileUri[0].fsPath; 448 } 449 return ''; 450 }); 451} 452 453function checkMode(name, genDir, mode, numberType, importIsCheck) { 454 name = re.replaceAll(name, ' ', ''); 455 if ('' === name) { 456 vscode.window.showErrorMessage('Please enter the path!'); 457 return; 458 } 459 if (mode === 0) { 460 if (name.indexOf('.') < 0) { 461 vscode.window.showErrorMessage('Please enter the correct file path!'); 462 return; 463 } 464 } else { 465 if (name.indexOf('.') > 0 || !fs.lstatSync(name).isDirectory()) { 466 vscode.window.showErrorMessage('Please enter the correct folder folder!'); 467 return; 468 } 469 } 470 471 if (exeFileExit()) { 472 executor(name, genDir, mode, numberType, importIsCheck); 473 } else { 474 vscode.window.showInformationMessage('Copy executable program to ' + __dirname); 475 } 476} 477 478// 获得配置页面和显示页面 479function getCommonWebViewContent(context, html) { 480 let data = getWebViewContent(context, html, showInfoPanel); 481 let content = data.toString(); 482 return content; 483} 484 485// this method is called when your extension is deactivated 486function deactivate() { } 487 488function getWebviewContent(context, importToolChain) { 489 let data = readFile(__dirname + '/vs_plugin_view.html'); 490 data = getWebViewContent(context, '/vs_plugin_view.html', globalPanel); 491 let content = data.toString(); 492 if (importToolChain) { 493 content = content.replace('Ok', 'Next'); 494 } 495 return content; 496} 497 498function getWebViewContent(context, templatePath, panel) { 499 const resourcePath = path.join(context.extensionPath, templatePath); 500 const dirPath = path.dirname(resourcePath); 501 let html = fs.readFileSync(resourcePath, 'utf-8'); 502 html = html.replace(/(<link.+?href="|<script.+?src="|<iframe.+?src="|<img.+?src=")(.+?)"/g, (m, $1, $2) => { 503 if ($2.indexOf('https://') < 0) { 504 return ($1 + panel.webview.asWebviewUri(vscode.Uri.file(path.resolve(dirPath, $2))) + '"'); 505 } else { 506 return $1 + $2 + '"'; 507 } 508 }); 509 return html; 510} 511 512module.exports = { 513 activate, 514 deactivate, 515}; 516