• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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