diff --git a/.gitignore b/.gitignore index a8e7cc30..77af049d 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ bin_bk # shell script *.sh + +# test +test diff --git a/.gitmodules b/.gitmodules index fecc9b31..876777b3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "res/eide_makefile_template"] - path = res/eide_makefile_template - url = git@github0null.io:root/eide_makefile_template.git [submodule "lib/node-utility"] path = lib/node-utility url = git@github.com:github0null/node-utility.git diff --git a/.vscodeignore b/.vscodeignore index 6c898204..63be0a06 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -38,6 +38,7 @@ bin/builder/*.xml bin/builder/*.exe.config bin/tools docs +test # shell script *.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a989dfa..0ca2f7ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,40 @@ *** +### [v2.15.0] (**requirements: VsCode ^1.60.0**) +- 新增:向**项目属性**视图增加一个**修改**按钮,允许以直接修改 yaml 配置文件的形式修改其配置,[文档](https://docs.em-ide.com/#/zh-cn/project_deps) +- 新增:向**项目资源**视图增加一个配置按钮,允许**为单个的文件或组增加任意编译选项**,支持使用 glob 模式匹配源文件和组,[文档](https://docs.em-ide.com/#/zh-cn/project_manager?id=为源文件附加单独的编译选项) +- 新增:为 **虚拟文件夹**/**源文件** 增加修改路径选项,允许修改文件的路径(方便直接修改整个虚拟文件夹树以及其链接到的源文件) +- 新增:将烧录选项 `程序文件` 的文件选择器取消,改为输入框;允许一次烧录多个程序文件,**程序文件** 字段格式 '`[,addr][;...]`' +- 新增:增加全片擦除功能,`ctrl+shift+p` 打开命令面板,输入 `Erase Chip`,即可执行(某些烧录器可能不支持,则该命令将退化为普通的烧录命令) +- 新增:在 **项目资源** 树中增加 `Output Files` 目录,用于查看生成的编译产物; 同时增加 axf, elf 信息查看功能,点击 axf/elf 文件即可打开 +- 新增:增加文件右键菜单项:打开所在目录 +- 新增:支持 armcc 反汇编查看,通过编辑器右键菜单 `查看反汇编` 即可打开 +- 新增:鼠标悬停显示文件,文件夹数量(仅虚拟文件夹) +- 新增:支持通过编写外部 js 脚本来导入其他IDE项目文件中的文件树,宏,头文件等信息(方便导入 `SEGGER Embedded Studio`,`IAR For ARM` 等其他任意 IDE 的项目),[文档](https://docs.em-ide.com/#/zh-cn/project_manager?id=从其他-ide-项目导入源文件资源) +- 新增:在输出目录生成 .map.view 文件,打开即可显示 map 文件的资源统计视图(仅支持ARMCC/GCC工程),[文档](https://docs.em-ide.com/#/zh-cn/utility_functions?id=查看程序资源视图) +- 新增:支持在线下载安装 Keil 芯片支持包,默认远程仓库地址:https://github.com/github0null/eide-cmsis-pack +- 修复:CMSIS Config Wizard 在解析不符合要求的字符串类型时,出现误判 +- 修复:删除 CMSIS 包之后,相应的 MCU 预定义宏被删除 +- **更改:调整某些配置的显示名称**,如 **项目依赖** 更改为 **项目属性** +- 更改:使用 Linux VT100 颜色代码添加更完备的编译输出日志高亮(**win10 以下的系统可能不支持**;可通过向插件配置 `Builder.AdditionalCommandLine` 添加 `-no-color` 关闭色彩输出) +- **更改:支持为 Armcc v5/v6 工具链单独设置编译器路径,同时保留旧的 MDK TOOLS.INI 设置方法** +- 更改:生成 *.obj 时,根据相对路径将 obj 生成至相应的文件夹树中(若有无法计算相对路径的文件,统一生成至 `obj` 目录) +- 优化:当烧录选项 `程序文件` 为空时,使用默认的程序文件路径(ui 上显示为 `${projectName}.hex`) +- 优化:允许带空格的虚拟文件夹命名 +- 优化:项目资源文件夹树排序显示 +- 优化:读取完 JLink Device 列表后,删除临时文件 +- 优化:调整构建工具的 Log 显示 +- 优化 CMSIS Wizard UI:使被禁用的子项表单控件无法被选中 +- 优化 CMSIS Wizard UI:调整布局,优化 vscode 主题颜色适配 +- 优化 完善 CMSIS Wizard 的语法支持程度 +*** + ### [v2.14.0] - 优化:增加一些编译器预定义宏 - 优化:优化 Builder Config UI,修复选项卡阴影区域显示不正常的问题 - 新增:支持显示源文件的头文件引用,默认开启,可在插件设置中关闭 -- 新增:增加 [CMSIS Configuration Wizard](https://arm-software.github.io/CMSIS_5/Pack/html/configWizard.html) 功能。 打开带有 CMSIS Config 格式的头文件,右键菜单选择 `CMSIS Configuration Wizard` 即可打开配置UI - ![preview](./img/cmsis_wizard_preview.png) +- 新增:增加 [CMSIS Configuration Wizard](https://arm-software.github.io/CMSIS_5/Pack/html/configWizard.html) 功能。 打开带有 CMSIS Config 格式的头文件,右键菜单选择 `CMSIS Configuration Wizard` 即可打开配置UI,[文档](https://docs.em-ide.com/#/zh-cn/cmsis_wizard) *** ### [v2.13.0] diff --git a/README.md b/README.md index 9ee3f40a..8f5d6723 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ ## 功能特性 🎉 -* 支持 8051,STM8,Cortex-M,RISC-V 项目 +* 支持开发 8051,STM8,Cortex-M,RISC-V 项目 * 支持导入 KEIL 项目 (仅支持 KEIL 5 及以上版本) * 支持安装标准的 KEIL 芯片支持包 (仅用于 Cortex-M 项目) * 提供丰富的项目模板方便快速开始项目 @@ -30,6 +30,8 @@ * 内置的串口监视器,一键打开串口 * 支持使用 Cppcheck 对项目进行静态检查 * 自动生成默认调试配置,为调试器插件 Cortex-debug / STM8-debug 生成默认配置 +* 支持编写 js 脚本来导入任意的 IDE 项目源文件资源 +* 内置多种实用工具,`CMSIS Config Wizard UI`, `反汇编查看`,`程序资源视图` ... *** diff --git a/lang/arm.v5.verify.json b/lang/arm.v5.verify.json index 233c8d01..eaddd649 100644 --- a/lang/arm.v5.verify.json +++ b/lang/arm.v5.verify.json @@ -163,7 +163,7 @@ }, "optimize-for-time": { "markdownDescription": "Optimize for time", - "description.zh-cn": "代码时间优化", + "description.zh-cn": "Optimize for time", "type": "boolean", "enum": [ true, @@ -233,7 +233,7 @@ }, "strict-ANSI-C": { "markdownDescription": "strict ANSI C", - "description.zh-cn": "使用标准(严格)的 ANSI C", + "description.zh-cn": "strict ANSI C", "type": "boolean", "enum": [ true, @@ -430,4 +430,4 @@ } } } -} \ No newline at end of file +} diff --git a/lang/axf.info.language-configuration.json b/lang/axf.info.language-configuration.json new file mode 100644 index 00000000..026b8baa --- /dev/null +++ b/lang/axf.info.language-configuration.json @@ -0,0 +1,9 @@ +{ + "comments": { + "lineComment": ";" + }, + "indentationRules": { + "increaseIndentPattern": "^[a-zA-Z_]\\w*\\s*:\\s*", + "decreaseIndentPattern": "^[a-zA-Z_]\\w*\\s*:\\s*" + } +} \ No newline at end of file diff --git a/lang/axf.info.tmLanguage.json b/lang/axf.info.tmLanguage.json new file mode 100644 index 00000000..28151d6c --- /dev/null +++ b/lang/axf.info.tmLanguage.json @@ -0,0 +1,59 @@ +{ + "information_for_contributors": [ + "armcc axf information" + ], + "version": "1.0.0", + "name": "axf.info", + "scopeName": "source.axf.info", + "patterns": [ + { + "include": "#elf_header" + }, + { + "include": "#keyword" + }, + { + "include": "#number" + }, + { + "include": "#string" + } + ], + "repository": { + "elf_header": { + "match": "^\\s*([\\w\\/][\\w\\/\\s]+):", + "captures": { + "1": { + "name": "entity.name.type" + } + } + }, + "keyword": { + "match": "\\(([A-Z_]+)\\)|(SHF_[\\w]+|PF_[\\w]+)", + "captures": { + "1": { + "name": "keyword.control.import" + }, + "2": { + "name": "keyword.control.import" + } + } + }, + "number": { + "match": "\\b([0-9a-f]+H|0x[0-9a-f]+|[0-9a-f]+)\\b", + "captures": { + "1": { + "name": "constant.numeric" + } + } + }, + "string": { + "match": "(\"[^\"]*\"|'[^']*')", + "captures": { + "1": { + "name": "string" + } + } + } + } +} \ No newline at end of file diff --git a/lang/elf.info.language-configuration.json b/lang/elf.info.language-configuration.json new file mode 100644 index 00000000..026b8baa --- /dev/null +++ b/lang/elf.info.language-configuration.json @@ -0,0 +1,9 @@ +{ + "comments": { + "lineComment": ";" + }, + "indentationRules": { + "increaseIndentPattern": "^[a-zA-Z_]\\w*\\s*:\\s*", + "decreaseIndentPattern": "^[a-zA-Z_]\\w*\\s*:\\s*" + } +} \ No newline at end of file diff --git a/lang/elf.info.tmLanguage.json b/lang/elf.info.tmLanguage.json new file mode 100644 index 00000000..5d5f6b32 --- /dev/null +++ b/lang/elf.info.tmLanguage.json @@ -0,0 +1,48 @@ +{ + "information_for_contributors": [ + "gnu elf information" + ], + "version": "1.0.0", + "name": "elf.info", + "scopeName": "source.elf.info", + "patterns": [ + { + "include": "#elf_header" + }, + { + "include": "#number" + }, + { + "include": "#string" + } + ], + "repository": { + "elf_header": { + "match": "^\\s*([\\w\\/][\\w\\/\\s]+):\\s+(.*)", + "captures": { + "1": { + "name": "entity.name.type" + }, + "2": { + "name": "string" + } + } + }, + "number": { + "match": "\\b([0-9a-f]+H|0x[0-9a-f]+|[0-9a-f]+)\\b", + "captures": { + "1": { + "name": "constant.numeric" + } + } + }, + "string": { + "match": "(\"[^\"]*\"|'[^']*')", + "captures": { + "1": { + "name": "string" + } + } + } + } +} \ No newline at end of file diff --git a/lang/stm8.iar.verify.json b/lang/stm8.iar.verify.json index 82d0e575..38a9b5bb 100644 --- a/lang/stm8.iar.verify.json +++ b/lang/stm8.iar.verify.json @@ -504,7 +504,7 @@ "linker-config": { "readable_name": "Linker config file path", "readable_name.zh-cn": "链接脚本路径", - "description": "linker config file path.\n Example: \"lnkstm8xxx.icf\"\n or \"./lnkstm8xxx.icf\" (relative by workspace folder)\n or \"${ToolDir}/stm8/config/lnkstm8xxx.icf\"", + "description": "linker config file path.\n Example: \"lnkstm8xxx.icf\"\n or \"./lnkstm8xxx.icf\" (relative by workspace folder)\n or \"${ToolchainRoot}/stm8/config/lnkstm8xxx.icf\"", "type": "string", "default": "lnkstm8s103f3.icf" }, diff --git a/lib/node-utility b/lib/node-utility index 6fdbedd7..c34a26b5 160000 --- a/lib/node-utility +++ b/lib/node-utility @@ -1 +1 @@ -Subproject commit 6fdbedd70d2b62b812e2d93498601c91e061edd5 +Subproject commit c34a26b5f589ff1aea8f2b46f9917b55ae4afb98 diff --git a/package.json b/package.json index 3cb0e749..12e95325 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "homepage": "https://github.com/github0null/eide/blob/master/README.md", "license": "MIT", "description": "A singlechip development environment for 8051/STM8/Cortex-M/RISC-V", - "version": "2.14.0", + "version": "2.15.0", "engines": { - "vscode": "^1.38.0" + "vscode": "^1.60.0" }, "categories": [ "Programming Languages", @@ -61,6 +61,7 @@ "@types/ftp": "^0.3.31", "@types/iconv-lite": "0.0.1", "@types/ini": "^1.3.30", + "@types/micromatch": "^4.0.2", "@types/mocha": "^5.2.7", "@types/node": "^12.12.17", "@types/vscode": "^1.38.0", @@ -76,11 +77,23 @@ "iconv-lite": "^0.5.0", "ini": "^2.0.0", "jsonc": "^2.0.0", + "micromatch": "^4.0.4", "unzipper": "^0.10.11", "x2js": "3.4.1", "yaml": "^1.10.2" }, "contributes": { + "customEditors": [ + { + "viewType": "cl.eide.map.view", + "displayName": "MapView", + "selector": [ + { + "filenamePattern": "*.map.view" + } + ] + } + ], "walkthroughs": [ { "id": "eide.startup.walkthroughs", @@ -130,6 +143,18 @@ { "title": "Embedded IDE", "properties": { + "EIDE.Option.ShowOutputFilesInExplorer": { + "type": "boolean", + "scope": "resource", + "markdownDescription": "%settings.option.show.output.files%", + "default": true + }, + "EIDE.Option.MapViewParserDepth": { + "type": "integer", + "scope": "resource", + "markdownDescription": "%settings.option.mapView.parser.depth%", + "default": 5 + }, "EIDE.Option.ShowSourceReferences": { "type": "boolean", "scope": "resource", @@ -188,7 +213,13 @@ "type": "boolean", "scope": "resource", "markdownDescription": "%settings.builder.gen.makefile.params%", - "default": true + "default": false + }, + "EIDE.Builder.AdditionalCommandLine": { + "type": "string", + "scope": "resource", + "markdownDescription": "Append additional commandline when invoke unify_builder", + "default": "" }, "EIDE.Option.ShowToolbarInEditerTitle": { "type": "boolean", @@ -214,18 +245,24 @@ "markdownDescription": "%settings.option.insert.extcommands.at.begin%", "default": false }, - "EIDE.Template.Repository.UseProxy": { + "EIDE.Repository.UseProxy": { "type": "boolean", "scope": "machine", - "markdownDescription": "%settings.template.use.proxy%", + "markdownDescription": "%settings.repo.use.proxy%", "default": true }, - "EIDE.Template.Repository.Url": { + "EIDE.Repository.Template.Url": { "type": "string", "scope": "machine", "markdownDescription": "%settings.template.repo.url%", "default": "github0null/eide-resource/contents/eide-template-list" }, + "EIDE.Repository.CmsisPack.Url": { + "type": "string", + "scope": "machine", + "markdownDescription": "%settings.cmsispack.repo.url%", + "default": "github0null/eide-cmsis-pack/contents/packages" + }, "EIDE.SerialPortMonitor.DefaultPort": { "type": "string", "scope": "resource", @@ -304,6 +341,18 @@ "markdownDescription": "gnu sdcc for stm8 installation path", "default": "${userRoot}/.eide/tools/stm8_gnu_sdcc" }, + "EIDE.ARM.ARMCC5.InstallDirectory": { + "type": "string", + "scope": "resource", + "markdownDescription": "%settings.arm.armcc5.install.folder%", + "default": "" + }, + "EIDE.ARM.ARMCC6.InstallDirectory": { + "type": "string", + "scope": "resource", + "markdownDescription": "%settings.arm.armcc6.install.folder%", + "default": "" + }, "EIDE.ARM.GCC.Prefix": { "type": "string", "scope": "resource", @@ -465,51 +514,75 @@ "title": "%eide.workspace.make.template%" }, { - "command": "_project.cppcheck.check_all", + "command": "_cl.eide.project.modify.deps", + "title": "%eide.item.modify%", + "icon": { + "dark": "./res/icon/ConfigurationFile_16x.svg", + "light": "./res/icon/ConfigurationFile_16x.svg" + } + }, + { + "command": "_cl.eide.project.import.ext.source.struct", + "title": "%eide.project.import.ext.project.src.struct%", + "icon": { + "dark": "./res/icon/Import_16x.svg", + "light": "./res/icon/Import_16x.svg" + } + }, + { + "command": "_cl.eide.project.cppcheck.check_all", "title": "%eide.project.run.cppcheck%" }, { - "command": "_project.cppcheck.clear_all", + "command": "_cl.eide.project.cppcheck.clear_all", "title": "%eide.project.clear.cppcheck%" }, { - "command": "_project.source.show_disassembly", + "command": "_cl.eide.project.source.show_disassembly", "title": "%eide.source.show.disassembly%" }, { - "command": "_project.source.show_cmsis_config_wizard", + "command": "_cl.eide.project.source.show_cmsis_config_wizard", "title": "%eide.source.show_cmsis_config_wizard%" }, { - "command": "_project.source.filesystem_folder_add_file", + "command": "_cl.eide.project.source.filesystem_folder_add_file", "title": "%eide.explorer.new.file%" }, { - "command": "_project.source.filesystem_folder_add", + "command": "_cl.eide.project.source.filesystem_folder_add", "title": "%eide.explorer.new.folder%" }, { - "command": "_project.source.virtual_folder_add_file", + "command": "_cl.eide.project.source.virtual_folder_add_file", "title": "%eide.explorer.add.file%" }, { - "command": "_project.source.virtual_folder_add", + "command": "_cl.eide.project.source.virtual_folder_add", "title": "%eide.explorer.add.folder%" }, { - "command": "_project.source.virtual_folder_remove", + "command": "_cl.eide.project.source.file.show.dir", + "title": "%eide.explorer.show.file.dir%" + }, + { + "command": "_cl.eide.project.source.modify.path", + "title": "%eide.explorer.modify.file.path%" + }, + { + "command": "_cl.eide.project.source.virtual_folder_remove", "title": "%eide.explorer.remove.folder%" }, { - "command": "_project.source.virtual_file_remove", + "command": "_cl.eide.project.source.virtual_file_remove", "title": "%eide.explorer.remove.file%" }, { - "command": "_project.source.virtual_folder_rename", + "command": "_cl.eide.project.source.virtual_folder_rename", "title": "%eide.explorer.rename.folder%" }, { - "command": "_project.refresh", + "command": "_cl.eide.project.refresh", "title": "%eide.project.refresh%", "icon": { "dark": "./res/icon/refresh-dark.svg", @@ -517,7 +590,7 @@ } }, { - "command": "_project.sourceRoot.refresh", + "command": "_cl.eide.project.sourceRoot.refresh", "title": "%eide.explorer.root.refresh.folder%", "icon": { "dark": "./res/icon/refresh-dark.svg", @@ -525,7 +598,7 @@ } }, { - "command": "_project.historyRecord", + "command": "_cl.eide.project.historyRecord", "title": "%eide.operation.open.history%", "icon": { "dark": "./res/icon/Note_16x.svg", @@ -533,11 +606,11 @@ } }, { - "command": "_project.clearHistoryRecord", + "command": "_cl.eide.project.clearHistoryRecord", "title": "%eide.operation.clear.history%" }, { - "command": "_project.saveAll", + "command": "_cl.eide.project.saveAll", "title": "%eide.project.save.all%", "icon": { "dark": "./res/icon/SaveAll_16x.svg", @@ -545,7 +618,7 @@ } }, { - "command": "_project.setActive", + "command": "_cl.eide.project.setActive", "title": "%eide.project.active%" }, { @@ -585,15 +658,24 @@ } }, { - "command": "_project.showBuildParams", + "command": "eide.project.flash.erase.all", + "title": "%eide.project.flash.erase.all%", + "category": "eide", + "icon": { + "dark": "./res/icon/TransferDownload_16x.svg", + "light": "./res/icon/TransferDownload_16x.svg" + } + }, + { + "command": "_cl.eide.project.showBuildParams", "title": "%eide.project.show.commands%" }, { - "command": "_project.generate.makefile", + "command": "_cl.eide.project.generate.makefile", "title": "%eide.project.gen.makefile%" }, { - "command": "_project.installCMSISHeaders", + "command": "_cl.eide.project.installCMSISHeaders", "title": "%eide.package.install.cmsis%", "icon": { "dark": "./res/icon/AddBuildQueue_16x.svg", @@ -601,7 +683,7 @@ } }, { - "command": "_project.addSrcDir", + "command": "_cl.eide.project.addSrcDir", "title": "%eide.explorer.root.add.folder%", "icon": { "dark": "./res/icon/AddFolder_16x.svg", @@ -609,7 +691,15 @@ } }, { - "command": "_project.removeSrcDir", + "command": "_cl.eide.project.modify.files.options", + "title": "%eide.project.modify.files.options%", + "icon": { + "dark": "./res/icon/ConfigurationFile_16x.svg", + "light": "./res/icon/ConfigurationFile_16x.svg" + } + }, + { + "command": "_cl.eide.project.removeSrcDir", "title": "%eide.explorer.root.remove.folder%", "icon": { "dark": "./res/icon/TestCoveredFailing_16x.svg", @@ -617,7 +707,7 @@ } }, { - "command": "_project.close", + "command": "_cl.eide.project.close", "title": "%eide.project.close%", "icon": { "dark": "./res/icon/TestCoveredFailing_16x.svg", @@ -625,7 +715,7 @@ } }, { - "command": "_project.addPackage", + "command": "_cl.eide.project.addPackage", "title": "%eide.package.install%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -633,7 +723,7 @@ } }, { - "command": "_project.removePackage", + "command": "_cl.eide.project.removePackage", "title": "%eide.package.remove%", "icon": { "dark": "./res/icon/TestCoveredFailing_16x.svg", @@ -641,7 +731,7 @@ } }, { - "command": "_project.setDevice", + "command": "_cl.eide.project.setDevice", "title": "%eide.package.select.device%", "icon": { "dark": "./res/icon/FieldAdded_16x.svg", @@ -649,7 +739,7 @@ } }, { - "command": "_project.exportXml", + "command": "_cl.eide.project.exportXml", "title": "%eide.project.export.keil%", "icon": { "dark": "./res/icon/ExportFile_16x.svg", @@ -657,7 +747,7 @@ } }, { - "command": "_project.modifyCompileConfig", + "command": "_cl.eide.project.modifyCompileConfig", "title": "%eide.item.modify%", "icon": { "dark": "./res/icon/Pen4_16x.svg", @@ -665,7 +755,7 @@ } }, { - "command": "_project.switchToolchain", + "command": "_cl.eide.project.switchToolchain", "title": "%eide.builder.switch%", "icon": { "dark": "./res/icon/SwitchSourceOrTarget_16x.svg", @@ -673,7 +763,7 @@ } }, { - "command": "_project.switchUploader", + "command": "_cl.eide.project.switchUploader", "title": "%eide.flash.switch%", "icon": { "dark": "./res/icon/SwitchSourceOrTarget_16x.svg", @@ -681,7 +771,7 @@ } }, { - "command": "_project.modifyUploadConfig", + "command": "_cl.eide.project.modifyUploadConfig", "title": "%eide.item.modify%", "icon": { "dark": "./res/icon/Pen4_16x.svg", @@ -689,7 +779,7 @@ } }, { - "command": "_project.modifyOtherSettings", + "command": "_cl.eide.project.modifyOtherSettings", "title": "%eide.item.modify%", "icon": { "dark": "./res/icon/Pen4_16x.svg", @@ -697,7 +787,7 @@ } }, { - "command": "_project.addIncludeDir", + "command": "_cl.eide.project.addIncludeDir", "title": "%eide.deps.include.add%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -705,7 +795,7 @@ } }, { - "command": "_project.addLibDir", + "command": "_cl.eide.project.addLibDir", "title": "%eide.deps.inclib.add%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -713,7 +803,7 @@ } }, { - "command": "_project.addDefine", + "command": "_cl.eide.project.addDefine", "title": "%eide.deps.macro.add%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -721,7 +811,7 @@ } }, { - "command": "_project.showIncludeDir", + "command": "_cl.eide.project.showIncludeDir", "title": "%eide.deps.include.show%", "icon": { "dark": "./res/icon/eye_16x_dark.svg", @@ -729,7 +819,7 @@ } }, { - "command": "_project.showLibDir", + "command": "_cl.eide.project.showLibDir", "title": "%eide.deps.inclib.show%", "icon": { "dark": "./res/icon/eye_16x_dark.svg", @@ -737,7 +827,7 @@ } }, { - "command": "_project.showDefine", + "command": "_cl.eide.project.showDefine", "title": "%eide.deps.macro.show%", "icon": { "dark": "./res/icon/eye_16x_dark.svg", @@ -745,7 +835,7 @@ } }, { - "command": "_project.excludeSource", + "command": "_cl.eide.project.excludeSource", "title": "%eide.explorer.exclude.file%", "icon": { "dark": "./res/icon/StatusNo_16x.svg", @@ -753,7 +843,7 @@ } }, { - "command": "_project.unexcludeSource", + "command": "_cl.eide.project.unexcludeSource", "title": "%eide.explorer.include.file%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -761,7 +851,7 @@ } }, { - "command": "_project.excludeFolder", + "command": "_cl.eide.project.excludeFolder", "title": "%eide.explorer.exclude.folder%", "icon": { "dark": "./res/icon/StatusNo_16x.svg", @@ -769,7 +859,7 @@ } }, { - "command": "_project.unexcludeFolder", + "command": "_cl.eide.project.unexcludeFolder", "title": "%eide.explorer.include.folder%", "icon": { "dark": "./res/icon/Add_16xMD.svg", @@ -777,7 +867,7 @@ } }, { - "command": "_project.removeDependenceItem", + "command": "_cl.eide.project.removeDependenceItem", "title": "%eide.item.remove%", "icon": { "dark": "./res/icon/TestCoveredFailing_16x.svg", @@ -785,7 +875,7 @@ } }, { - "command": "_project.importDependenceFromPack", + "command": "_cl.eide.project.importDependenceFromPack", "title": "%eide.package.comp.install%", "icon": { "dark": "./res/icon/AddToCollection_16x.svg", @@ -793,7 +883,7 @@ } }, { - "command": "_project.removeDependenceFromPack", + "command": "_cl.eide.project.removeDependenceFromPack", "title": "%eide.package.comp.uninstall%", "icon": { "dark": "./res/icon/TestCoveredFailing_16x.svg", @@ -801,7 +891,7 @@ } }, { - "command": "_project.copyItemValue", + "command": "_cl.eide.project.copyItemValue", "title": "%eide.item.copy%", "icon": { "dark": "./res/icon/CopyToClipboard_16x.svg", @@ -809,7 +899,7 @@ } }, { - "command": "_project.switchMode", + "command": "_cl.eide.project.switchMode", "title": "%eide.project.switch.target%", "icon": { "dark": "./res/icon/SwitchSourceOrTarget_16x.svg", @@ -817,7 +907,7 @@ } }, { - "command": "_project.exportAsTemplate", + "command": "_cl.eide.project.exportAsTemplate", "title": "%eide.project.export.template%", "icon": { "dark": "./res/icon/TableToFile_16x.svg", @@ -862,7 +952,7 @@ "when": "cl.eide.projectActived && !isInDiffEditor" }, { - "command": "_project.cppcheck.check_all", + "command": "_cl.eide.project.cppcheck.check_all", "key": "ctrl+alt+c", "when": "cl.eide.projectActived" } @@ -885,21 +975,21 @@ "when": "view == Project && cl.eide.isWorkspaceProject" }, { - "command": "_project.refresh", + "command": "_cl.eide.project.refresh", "group": "navigation", "when": "view == Project" }, { - "command": "_project.historyRecord", + "command": "_cl.eide.project.historyRecord", "group": "navigation", "when": "view == Operation" }, { - "command": "_project.clearHistoryRecord", + "command": "_cl.eide.project.clearHistoryRecord", "when": "view == Operation" }, { - "command": "_project.saveAll", + "command": "_cl.eide.project.saveAll", "group": "navigation", "when": "view == Project" } @@ -923,12 +1013,12 @@ ], "editor/context": [ { - "command": "_project.source.show_disassembly", + "command": "_cl.eide.project.source.show_disassembly", "group": "commands", "when": "resourceLangId =~ /^c$|^cpp$/ && cl.eide.projectActived && !config.EIDE.Option.DisableEditorContextMenu" }, { - "command": "_project.source.show_cmsis_config_wizard", + "command": "_cl.eide.project.source.show_cmsis_config_wizard", "group": "commands", "when": "resourceLangId =~ /^c$|^cpp$/ && !config.EIDE.Option.DisableEditorContextMenu" } @@ -940,90 +1030,98 @@ "when": "resourceLangId =~ /^c$|^cpp$/ && cl.eide.projectActived && !config.EIDE.Option.DisableExplorerContextMenu" }, { - "command": "_project.source.show_disassembly", + "command": "_cl.eide.project.source.show_disassembly", "group": "commands", "when": "resourceLangId =~ /^c$|^cpp$/ && cl.eide.projectActived && !config.EIDE.Option.DisableExplorerContextMenu" }, { - "command": "_project.source.show_cmsis_config_wizard", + "command": "_cl.eide.project.source.show_cmsis_config_wizard", "group": "commands", "when": "resourceLangId =~ /^c$|^cpp$/ && !config.EIDE.Option.DisableExplorerContextMenu" } ], "view/item/context": [ { - "command": "_project.cppcheck.check_all", + "command": "_cl.eide.project.cppcheck.check_all", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.cppcheck.clear_all", + "command": "_cl.eide.project.cppcheck.clear_all", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.source.filesystem_folder_add_file", + "command": "_cl.eide.project.source.filesystem_folder_add_file", "when": "viewItem == FOLDER || viewItem == FOLDER_ROOT && view == Project" }, { - "command": "_project.source.filesystem_folder_add", + "command": "_cl.eide.project.source.filesystem_folder_add", "when": "viewItem == FOLDER || viewItem == FOLDER_ROOT && view == Project" }, { - "command": "_project.source.virtual_folder_add_file", - "when": "viewItem == V_FOLDER_ROOT || viewItem == V_FOLDER || viewItem == V_EXCFOLDER && view == Project" + "command": "_cl.eide.project.source.virtual_folder_add_file", + "when": "viewItem == PROJECT || viewItem == V_FOLDER_ROOT || viewItem == V_FOLDER || viewItem == V_EXCFOLDER && view == Project" }, { - "command": "_project.source.virtual_folder_add", + "command": "_cl.eide.project.source.virtual_folder_add", "when": "viewItem == V_FOLDER_ROOT || viewItem == V_FOLDER || viewItem == V_EXCFOLDER && view == Project" }, { - "command": "_project.source.virtual_folder_remove", + "command": "_cl.eide.project.source.virtual_folder_remove", "when": "viewItem == V_FOLDER_ROOT || viewItem == V_FOLDER || viewItem == V_EXCFOLDER && view == Project" }, { - "command": "_project.source.virtual_folder_rename", + "command": "_cl.eide.project.source.virtual_folder_rename", "when": "viewItem == V_FOLDER_ROOT || viewItem == V_FOLDER || viewItem == V_EXCFOLDER && view == Project" }, { - "command": "_project.source.virtual_file_remove", + "command": "_cl.eide.project.source.virtual_file_remove", "when": "viewItem == V_FILE_ITEM || viewItem == V_EXCFILE_ITEM && view == Project" }, { - "command": "_project.addPackage", + "command": "_cl.eide.project.source.file.show.dir", + "when": "viewItem == OUTPUT_FILE_ITEM || viewItem == FILE_ITEM || viewItem == EXCFILE_ITEM || viewItem == V_FILE_ITEM || viewItem == V_EXCFILE_ITEM && view == Project" + }, + { + "command": "_cl.eide.project.source.modify.path", + "when": "viewItem == V_FOLDER || viewItem == V_FOLDER_ROOT || viewItem == V_FILE_ITEM || viewItem == V_EXCFILE_ITEM && view == Project" + }, + { + "command": "_cl.eide.project.addPackage", "group": "inline", "when": "viewItem == PACK && view == Project" }, { - "command": "_project.removePackage", + "command": "_cl.eide.project.removePackage", "group": "inline", "when": "viewItem == PACK_GROUP && view == Project" }, { - "command": "_project.setDevice", + "command": "_cl.eide.project.setDevice", "group": "inline", "when": "viewItem == PACK_GROUP && view == Project" }, { - "command": "_project.addPackage", + "command": "_cl.eide.project.addPackage", "when": "viewItem == PACK && view == Project" }, { - "command": "_project.installCMSISHeaders", + "command": "_cl.eide.project.installCMSISHeaders", "when": "viewItem == PACK && view == Project" }, { - "command": "_project.setActive", + "command": "_cl.eide.project.setActive", "when": "viewItem == SOLUTION && view == Project && cl.eide.enable.active" }, { - "command": "_project.switchMode", + "command": "_cl.eide.project.switchMode", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.exportAsTemplate", + "command": "_cl.eide.project.exportAsTemplate", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.exportXml", + "command": "_cl.eide.project.exportXml", "when": "viewItem == SOLUTION && view == Project" }, { @@ -1042,11 +1140,11 @@ "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.showBuildParams", + "command": "_cl.eide.project.showBuildParams", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.generate.makefile", + "command": "_cl.eide.project.generate.makefile", "when": "viewItem == SOLUTION && view == Project" }, { @@ -1055,120 +1153,142 @@ "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.close", + "command": "_cl.eide.project.close", "when": "viewItem == SOLUTION && view == Project" }, { - "command": "_project.excludeSource", + "command": "_cl.eide.project.excludeSource", "group": "inline", "when": "viewItem == FILE_ITEM || viewItem == V_FILE_ITEM && view == Project" }, { - "command": "_project.unexcludeSource", + "command": "_cl.eide.project.unexcludeSource", "group": "inline", "when": "viewItem == EXCFILE_ITEM || viewItem == V_EXCFILE_ITEM && view == Project" }, { - "command": "_project.excludeFolder", + "command": "_cl.eide.project.excludeFolder", "group": "inline", "when": "viewItem == FOLDER || viewItem == V_FOLDER || viewItem == V_FOLDER_ROOT && view == Project" }, { - "command": "_project.unexcludeFolder", + "command": "_cl.eide.project.unexcludeFolder", "group": "inline", "when": "viewItem == EXCFOLDER || viewItem == V_EXCFOLDER && view == Project" }, { - "command": "_project.addSrcDir", + "command": "_cl.eide.project.addSrcDir", + "group": "inline", + "when": "viewItem == PROJECT && view == Project" + }, + { + "command": "_cl.eide.project.addSrcDir", + "when": "viewItem == PROJECT && view == Project" + }, + { + "command": "_cl.eide.project.modify.files.options", "group": "inline", "when": "viewItem == PROJECT && view == Project" }, { - "command": "_project.addSrcDir", + "command": "_cl.eide.project.modify.files.options", + "when": "viewItem == PROJECT && view == Project" + }, + { + "command": "_cl.eide.project.import.ext.source.struct", "when": "viewItem == PROJECT && view == Project" }, { - "command": "_project.sourceRoot.refresh", + "command": "_cl.eide.project.sourceRoot.refresh", "group": "inline", - "when": "viewItem == FOLDER_ROOT_DEPS || viewItem == FOLDER_ROOT || viewItem == V_FOLDER_ROOT && view == Project" + "when": "viewItem == OUTPUT_FOLDER || viewItem == FOLDER_ROOT_DEPS || viewItem == FOLDER_ROOT || viewItem == V_FOLDER_ROOT && view == Project" }, { - "command": "_project.removeSrcDir", + "command": "_cl.eide.project.removeSrcDir", "group": "inline", "when": "viewItem == FOLDER_ROOT && view == Project" }, { - "command": "_project.switchToolchain", + "command": "_cl.eide.project.switchToolchain", "group": "inline", "when": "viewItem == COMPILE_CONFIGURATION && view == Project" }, { - "command": "_project.switchUploader", + "command": "_cl.eide.project.switchUploader", "group": "inline", "when": "viewItem == UPLOAD_OPTION_C51 || viewItem == UPLOAD_OPTION" }, { - "command": "_project.modifyCompileConfig", + "command": "_cl.eide.project.modifyCompileConfig", "group": "inline", "when": "viewItem == COMPILE_CONFIGURATION_ITEM && view == Project" }, { - "command": "_project.modifyUploadConfig", + "command": "_cl.eide.project.modifyUploadConfig", "group": "inline", "when": "viewItem == UPLOAD_OPTION_ITEM && view == Project" }, { - "command": "_project.modifyOtherSettings", + "command": "_cl.eide.project.modifyOtherSettings", "group": "inline", "when": "viewItem == SETTINGS_ITEM && view == Project" }, { - "command": "_project.addIncludeDir", + "command": "_cl.eide.project.modify.deps", + "group": "inline", + "when": "viewItem == DEPENDENCE && view == Project" + }, + { + "command": "_cl.eide.project.modify.deps", + "when": "viewItem == DEPENDENCE && view == Project" + }, + { + "command": "_cl.eide.project.addIncludeDir", "group": "inline", "when": "viewItem == INC_GROUP && view == Project" }, { - "command": "_project.showIncludeDir", + "command": "_cl.eide.project.showIncludeDir", "group": "inline", "when": "viewItem == INC_GROUP && view == Project" }, { - "command": "_project.addLibDir", + "command": "_cl.eide.project.addLibDir", "group": "inline", "when": "viewItem == LIB_GROUP && view == Project" }, { - "command": "_project.showLibDir", + "command": "_cl.eide.project.showLibDir", "group": "inline", "when": "viewItem == LIB_GROUP && view == Project" }, { - "command": "_project.addDefine", + "command": "_cl.eide.project.addDefine", "group": "inline", "when": "viewItem == DEFINE_GROUP && view == Project" }, { - "command": "_project.showDefine", + "command": "_cl.eide.project.showDefine", "group": "inline", "when": "viewItem == DEFINE_GROUP && view == Project" }, { - "command": "_project.removeDependenceItem", + "command": "_cl.eide.project.removeDependenceItem", "group": "inline", "when": "viewItem == INC_ITEM || viewItem == DEFINE_ITEM || viewItem == LIB_ITEM || viewItem == SOURCE_ITEM && view == Project" }, { - "command": "_project.copyItemValue", + "command": "_cl.eide.project.copyItemValue", "group": "inline", "when": "viewItem == EXCFILE_ITEM || viewItem == FILE_ITEM || viewItem == SRCREF_FILE_ITEM || viewItem == ITEM || viewItem == DEPENDENCE_ITEM || viewItem == INC_ITEM || viewItem == DEFINE_ITEM || viewItem == LIB_ITEM || viewItem == SOURCE_ITEM && view == Project" }, { - "command": "_project.importDependenceFromPack", + "command": "_cl.eide.project.importDependenceFromPack", "group": "inline", "when": "viewItem == COMPONENT_GROUP && view == Project" }, { - "command": "_project.removeDependenceFromPack", + "command": "_cl.eide.project.removeDependenceFromPack", "group": "inline", "when": "viewItem == ACTIVED_GROUP && view == Project" } @@ -1215,6 +1335,32 @@ "**/*.edasm" ], "configuration": "./lang/edasm.language-configuration.json" + }, + { + "id": "axf.info", + "aliases": [ + "Armcc Axf Info" + ], + "extensions": [ + ".axf.info" + ], + "filenamePatterns": [ + "**/*.axf.info" + ], + "configuration": "./lang/axf.info.language-configuration.json" + }, + { + "id": "elf.info", + "aliases": [ + "Gnu Elf Info" + ], + "extensions": [ + ".elf.info" + ], + "filenamePatterns": [ + "**/*.elf.info" + ], + "configuration": "./lang/elf.info.language-configuration.json" } ], "jsonValidation": [ @@ -1270,6 +1416,16 @@ "language": "edasm", "scopeName": "source.dasm.gcc", "path": "./lang/edasm.tmLanguage.json" + }, + { + "language": "axf.info", + "scopeName": "source.axf.info", + "path": "./lang/axf.info.tmLanguage.json" + }, + { + "language": "elf.info", + "scopeName": "source.elf.info", + "path": "./lang/elf.info.tmLanguage.json" } ], "problemMatchers": [ @@ -1303,4 +1459,4 @@ } ] } -} \ No newline at end of file +} diff --git a/package.nls.json b/package.nls.json index ee54aa98..12d882bb 100644 --- a/package.nls.json +++ b/package.nls.json @@ -16,7 +16,7 @@ "eide.project.save.all": "Save All Projects", "eide.project.active": "Active Project", "eide.project.close": "Close", - "eide.project.export.template": "Export Eide Template", + "eide.project.export.template": "Export Eide Project Template", "eide.project.export.keil": "Export Keil Project", "eide.project.show.commands": "Show Compiler CommandLine", "eide.project.switch.target": "Switch Target", @@ -24,7 +24,17 @@ "eide.project.rebuild": "Rebuild", "eide.project.clean": "Clean", "eide.project.upload": "Upload Program To Device", + "eide.project.flash.erase.all": "Erase Chip", "eide.project.gen.makefile": "Generate Makefile Template", + "eide.project.modify.files.options": "Append separate compiler options to the file", + "eide.project.import.ext.project.src.struct": "Import source file resources from other IDE's project", + + "eide.prj.menus.main.cppcheck": "Static Check", + "eide.prj.menus.sub.cppcheck.run": "Check Project by Cppcheck", + "eide.prj.menus.sub.cppcheck.clear": "Clear Cppcheck Diagnostic Informations", + "eide.prj.menus.main.export": "Export As ...", + "eide.prj.menus.main.export.keil": "Keil Project", + "eide.prj.menus.main.export.eide": "Eide Project Template", "eide.workspace.build": "Build All", "eide.workspace.rebuild": "Rebuild All", @@ -48,6 +58,8 @@ "eide.explorer.rename.folder": "Rename", "eide.explorer.exclude.folder": "Exclude Folder", "eide.explorer.include.folder": "Include Folder", + "eide.explorer.show.file.dir": "Show In File Explorer", + "eide.explorer.modify.file.path": "Modify File Path", "eide.source.show_cmsis_config_wizard": "CMSIS Configuration Wizard", "eide.source.show.disassembly": "Show Disassembly", @@ -81,9 +93,12 @@ "settings.option.use.task.to.build": "Use Task to build project instead of Terminal", "settings.option.insert.extcommands.at.begin": "The built-in command lines will insert at the beginning of the `beforeBuildTasks / afterBuildTasks`", "settings.option.show.source.references": "Show c/c++ header references for source file", + "settings.option.mapView.parser.depth": "The depth limit of the obj file path when *.map.view is displayed. Obj files larger than this depth are ignored.", + "settings.option.show.output.files": "Show output files in project resource explorer", - "settings.template.use.proxy": "Use github proxy to improve network speed (experimental function)", - "settings.template.repo.url": "remote public github repository url, format: ``/``/contents/``", + "settings.repo.use.proxy": "Use proxy to improve network speed for github (experimental function)", + "settings.template.repo.url": "Public github template repository url, format: ``/``/contents/``", + "settings.cmsispack.repo.url": "Public github cmsis-pack repository url, format: ``/``/contents/``", "settings.serial.def.port": "Default port name", "settings.serial.show.toolbar": "Show serialport status bar", @@ -97,6 +112,8 @@ "settings.iar.stm8.install.folder": "IAR for STM8 install directory [`example: D:\\IAR`]", "settings.sdcc.install.folder": "SDCC install folder [`example: C:\\Program Files (x86)\\SDCC`]", + "settings.arm.armcc5.install.folder": "Armcc v5 toolchain install directory [`example: D:\\ac5`]", + "settings.arm.armcc6.install.folder": "Armcc v6 toolchain install directory [`example: D:\\ac6`]", "settings.arm.gcc.install.folder": "GCC for ARM toolchain install directory [`example: D:\\arm-gcc\\8 2019-q3-update`]", "settings.arm.gcc.prefix": "ARM GCC toolchain prefix [`example: arm-none-eabi-`]", diff --git a/package.nls.zh-CN.json b/package.nls.zh-CN.json index a271761a..67929331 100644 --- a/package.nls.zh-CN.json +++ b/package.nls.zh-CN.json @@ -24,7 +24,10 @@ "eide.project.rebuild": "重新构建", "eide.project.clean": "清理", "eide.project.upload": "烧录", + "eide.project.flash.erase.all": "擦除芯片", "eide.project.gen.makefile": "生成 Makefile 模板", + "eide.project.modify.files.options": "为文件附加单独的编译选项", + "eide.project.import.ext.project.src.struct": "从其他 IDE 的项目中导入源文件资源", "eide.workspace.build": "构建所有项目", "eide.workspace.rebuild": "重新构建所有项目", @@ -48,6 +51,8 @@ "eide.explorer.rename.folder": "重命名", "eide.explorer.exclude.folder": "从项目中排除", "eide.explorer.include.folder": "取消排除", + "eide.explorer.show.file.dir": "在文件浏览器中显示", + "eide.explorer.modify.file.path": "修改源文件路径", "eide.source.show_cmsis_config_wizard": "CMSIS Configuration Wizard", "eide.source.show.disassembly": "查看反汇编", @@ -81,8 +86,9 @@ "settings.option.use.task.to.build": "使用 vscode 任务执行编译命令", "settings.option.insert.extcommands.at.begin": "内置的构建任务将被插入到 `beforeBuildTasks / afterBuildTasks` 之前", - "settings.template.use.proxy": "使用 github 代理加速模板下载", - "settings.template.repo.url": "远程 github 模板仓库地址, 格式: ``/``/contents/``", + "settings.repo.use.proxy": "使用代理加速 github 资源下载", + "settings.template.repo.url": "Github 模板仓库地址, 格式: ``/``/contents/``", + "settings.cmsispack.repo.url": "Github cmsis 包仓库地址, 格式: ``/``/contents/``", "settings.serial.def.port": "默认串口名称", "settings.serial.show.toolbar": "显示串口工具栏", @@ -96,6 +102,8 @@ "settings.iar.stm8.install.folder": "IAR for STM8 安装目录 [`example: D:\\IAR`]", "settings.sdcc.install.folder": "SDCC 安装目录 [`example: C:\\Program Files (x86)\\SDCC`]", + "settings.arm.armcc5.install.folder": "Armcc v5 toolchain 安装目录 [`example: D:\\ac5`]", + "settings.arm.armcc6.install.folder": "Armcc v6 toolchain 安装目录 [`example: D:\\ac6`]", "settings.arm.gcc.install.folder": "GNU Arm Embedded Toolchain 安装目录 [`example: D:\\arm-gcc\\8 2019-q3-update`]", "settings.arm.gcc.prefix": "GNU Arm Embedded Toolchain 编译器前缀 [`example: arm-none-eabi-`]", @@ -117,6 +125,8 @@ "settings.option.disable.editor_context.menu": "禁用编辑器上下文菜单", "settings.option.disable.explorer_context.menu": "禁用资源浏览器上下文菜单", "settings.option.show.source.references": "显示源文件的头文件引用", + "settings.option.mapView.parser.depth": "显示 *.map.view 时,obj 文件路径的深度限制。大于该深度的 obj 文件将被忽略", + "settings.option.show.output.files": "在项目资源管理器中显示编译输出文件", "string.eide.startup.walkthroughs": "Embedded IDE 演示", "string.eide.startup.walkthroughs.detail": "我们将向您展示如何开始使用 Embedded IDE 。", diff --git a/res/data/config.yaml b/res/data/config.yaml new file mode 100644 index 00000000..3a3d35ca --- /dev/null +++ b/res/data/config.yaml @@ -0,0 +1,5 @@ +# eide config + +version: '1.0' + +binaray_version: '2.14.1' diff --git a/res/data/template.files.options.yml b/res/data/template.files.options.yml new file mode 100644 index 00000000..1c640b11 --- /dev/null +++ b/res/data/template.files.options.yml @@ -0,0 +1,35 @@ +######################################################################################### +# append compiler options for files or virtual folders +######################################################################################### + +version: '1.0' + +######################################################################################### +# rules +# +# syntax: +# : +# +# examples: +# 'main.cpp': --cpp11 -Og ... +# 'src/*.c': -gnu -O2 ... +# 'src/lib/**/*.cpp': --cpp11 -Os ... +# '!Application/*.c': -O0 +# '**/*.c': -O2 -gnu ... +# +# For more syntax, please refer to: https://www.npmjs.com/package/micromatch +# +######################################################################################### + +# +# for source files with filesystem paths +# +files: +# './test/**/*.c': --c99 + +# +# for source files with virtual paths +# +virtualPathFiles: +# 'virtual_folder/**/*.c': --c99 + diff --git a/res/eide_makefile_template b/res/eide_makefile_template deleted file mode 160000 index 771d32b4..00000000 --- a/res/eide_makefile_template +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 771d32b4ffc8d12e75ef15df3e3052135651f5ac diff --git a/res/html/StorageLayoutView/index.html b/res/html/StorageLayoutView/index.html index 72b0a242..846583e9 100644 --- a/res/html/StorageLayoutView/index.html +++ b/res/html/StorageLayoutView/index.html @@ -31,13 +31,35 @@ list-style-type: none; } - #submit { + #header { + position: -webkit-sticky; /* Safari */ + position: sticky; + padding: 12px; + top: 0px; + z-index: 10; + background-color: var(--vscode-editor-background); + } + + #header-cont { display: flex; - justify-content: center; + align-items: center; + justify-content: space-between; + } + + #button-cont { + display: flex; + justify-items: center; + justify-content: flex-end; + } + + h3 { + margin: 10px 4px; + color: var(--vscode-editor-foreground) !important; } button { margin: 4px; + margin-left: 8px; } .form-group { @@ -266,6 +288,15 @@ + - -
- - -
diff --git a/res/icon/Report_16x.svg b/res/icon/Report_16x.svg new file mode 100644 index 00000000..d29e5f5f --- /dev/null +++ b/res/icon/Report_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/icon/file_type_map.svg b/res/icon/file_type_map.svg new file mode 100644 index 00000000..660b7649 --- /dev/null +++ b/res/icon/file_type_map.svg @@ -0,0 +1 @@ +file_type_map \ No newline at end of file diff --git a/res/icon/folder_type_binary.svg b/res/icon/folder_type_binary.svg new file mode 100644 index 00000000..d19e36c0 --- /dev/null +++ b/res/icon/folder_type_binary.svg @@ -0,0 +1 @@ +folder_type_binary \ No newline at end of file diff --git a/src/CodeBuilder.ts b/src/CodeBuilder.ts index eccc5631..9ae92386 100644 --- a/src/CodeBuilder.ts +++ b/src/CodeBuilder.ts @@ -22,6 +22,12 @@ SOFTWARE. */ +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as NodePath from 'path'; +import * as events from 'events'; +import * as globmatch from 'micromatch' + import { AbstractProject } from "./EIDEProject"; import { ResManager } from "./ResManager"; import { File } from "../lib/node-utility/File"; @@ -35,9 +41,6 @@ import { GlobalEvent } from "./GlobalEvents"; import { ExceptionToMessage, newMessage } from "./Message"; import { CmdLineHandler } from "./CmdLineHandler"; -import * as fs from 'fs'; -import * as vscode from 'vscode'; -import * as NodePath from 'path'; import { ArrayDelRepetition } from "../lib/node-utility/Utility"; import { DependenceManager } from "./DependenceManager"; import { WorkspaceManager } from "./WorkspaceManager"; @@ -45,7 +48,6 @@ import { ToolchainName } from "./ToolchainManager"; import { md5, sha256 } from "./utility"; import { MakefileGen } from "./Makefile"; import { append2SysEnv } from "./Platform"; -import * as events from 'events'; import { FileWatcher } from "../lib/node-utility/FileWatcher"; export interface BuildOptions { @@ -68,6 +70,8 @@ export interface BuilderParams { ram?: number; rom?: number; sourceList: string[]; + sourceParams?: { [name: string]: string; }; + sourceParamsMtime?: number; incDirs: string[]; libDirs: string[]; defines: string[]; @@ -101,23 +105,97 @@ export abstract class CodeBuilder { this._event.emit(event, arg); } - getSourceList(): string[] { + genSourceInfo(prevBuilderParams: BuilderParams): { + sources: string[], + params?: { [name: string]: string; } + paramsModTime?: number; + } { - const srcList: string[] = []; + const srcList: { path: string, virtualPath?: string; }[] = []; + const srcParams: { [name: string]: string; } = {}; const fGoups = this.project.getFileGroups(); const filter = AbstractProject.getSourceFileFilter(); + // filter source files for (const group of fGoups) { if (group.disabled) continue; // skip disabled group for (const source of group.files) { if (source.disabled) continue; // skip disabled file if (!filter.some((reg) => reg.test(source.file.path))) continue; // skip non-source const rePath = this.project.ToRelativePath(source.file.path, false); - srcList.push(rePath || source.file.path); + const fInfo: any = { path: rePath || source.file.path } + if (AbstractProject.isVirtualSourceGroup(group)) { + fInfo.virtualPath = `${group.name}/${source.file.name}` + } + srcList.push(fInfo); } } - return srcList; + // append user options for files + try { + const options = this.project.getFilesOptions(); + + // parser + const matcher = (parttenInfo: any, fieldName: string) => { + srcList.forEach((srcInf: any) => { + if (!srcInf[fieldName]) return; // skip if not exist + for (const expr in parttenInfo) { + const path = (srcInf[fieldName]).replace(/\\/g, '/'); + if (globmatch.isMatch(path, expr)) { + const val = parttenInfo[expr]?.trim().replace(/(?:\r\n|\n)$/, '') + if (srcParams[srcInf.path]) { + srcParams[srcInf.path] += ` ${val || ''}` + } else { + srcParams[srcInf.path] = val || ''; + } + } + } + }); + }; + + if (options) { + + // virtual folder files + if (typeof options?.virtualPathFiles == 'object') { + const parttenInfo = options?.virtualPathFiles; + matcher(parttenInfo, 'virtualPath'); + } + + // filesystem files + if (typeof options?.files == 'object') { + const parttenInfo = options?.files; + matcher(parttenInfo, 'path'); + } + + // if src options is modified to null but old is not null, + // we need make source recompile + const oldSrcParams = prevBuilderParams.sourceParams; + for (const path in oldSrcParams) { + if (srcParams[path] == undefined && oldSrcParams[path] != undefined && + oldSrcParams[path] != '') { + srcParams[path] = ""; // make it empty to trigger recompile + } + } + } + + } catch (err) { + GlobalEvent.emit('msg', ExceptionToMessage(err, 'Hidden')); + GlobalEvent.emit('msg', newMessage('Warning', `Append files options failed !, msg: ${err.message || ''}`)); + } + + let mTimeMs: number | undefined; + + try { + mTimeMs = fs.statSync(this.project.getFilesOptionsFile().path).mtimeMs + } catch (error) { + // do nothing + } + + return { + sources: srcList.map((inf) => inf.path), + params: srcParams, + paramsModTime: mTimeMs + } } getIncludeDirs(): string[] { @@ -270,6 +348,9 @@ export abstract class CodeBuilder { .compileConfigModel.getOptions(this.project.getEideDir().path, config); const memMaxSize = this.getMaxSize(); const modeList: string[] = []; + const oldParamsPath = `${paramsPath}.old`; + const prevParams: BuilderParams = File.IsFile(oldParamsPath) ? JSON.parse(fs.readFileSync(oldParamsPath, 'utf8')) : {}; + const sourceInfo = this.genSourceInfo(prevParams); const builderOptions: BuilderParams = { name: config.name, @@ -285,7 +366,9 @@ export abstract class CodeBuilder { rom: memMaxSize?.rom, incDirs: this.getIncludeDirs().map((incPath) => { return this.project.ToRelativePath(incPath, false) || incPath; }), libDirs: this.getLibDirs().map((libPath) => { return this.project.ToRelativePath(libPath, false) || libPath; }), - sourceList: this.getSourceList().sort(), + sourceList: sourceInfo.sources.sort(), + sourceParams: sourceInfo.params, + sourceParamsMtime: sourceInfo.paramsModTime, defines: this.getDefineList(), options: JSON.parse(JSON.stringify(compileOptions)), env: this.project.getProjectEnv() @@ -321,9 +404,6 @@ export abstract class CodeBuilder { // check whether need rebuild project if (this.isRebuild() == false) { try { - const prevParams: BuilderParams = - File.IsFile(paramsPath) ? JSON.parse(fs.readFileSync(paramsPath, 'utf8')) : {}; - // not found hash from old params file if (prevParams.sha == undefined) { this.enableRebuild(); @@ -363,11 +443,7 @@ export abstract class CodeBuilder { } // write project build params - if (this.useShowParamsMode) { - fs.writeFileSync(paramsPath, JSON.stringify(builderOptions, undefined, 4)); - } else { - fs.writeFileSync(paramsPath, JSON.stringify(builderOptions)); - } + fs.writeFileSync(paramsPath, JSON.stringify(builderOptions, undefined, 4)); // generate makefile params if (settingManager.isGenerateMakefileParams()) { @@ -379,12 +455,19 @@ export abstract class CodeBuilder { } } - return [ + let cmds = [ '-b', binDir, '-M', toolchain.modelName, '-p', paramsPath, '-m', modeList.join('-') ]; + + const extraCmd = settingManager.getBuilderAdditionalCommandLine()?.trim(); + if (extraCmd) { + cmds = cmds.concat(extraCmd.split(/\s+/)); + } + + return cmds; } private getBuilderExe(): File { @@ -805,14 +888,13 @@ class ARMCodeBuilder extends CodeBuilder { // convert elf if (['AC5', 'AC6'].includes(config.toolchain) && settingManager.IsConvertAxf2Elf()) { - const toolFolderName = config.toolchain === 'AC6' ? 'ARMCLANG' : 'ARMCC'; - const tool_root_folder = `${toolchain.getToolchainDir().path}\\${toolFolderName}`; + const tool_root_folder = toolchain.getToolchainDir().path; const ouput_path = `\${outDir}\\${config.name}`; const axf2elf_log = `\${outDir}\\axf2elf.log`; extraCommands.push({ name: 'axf to elf', - command: `"\${exeDir}\\mono.exe" "\${exeDir}\\axf2elf.exe" -d "${tool_root_folder}" -b "${ouput_path}.bin" -i "${ouput_path}.axf" -o "${ouput_path}.elf" > "${axf2elf_log}"` + command: `"\${BuilderFolder}\\mono.exe" "\${BuilderFolder}\\axf2elf.exe" -d "${tool_root_folder}" -b "${ouput_path}.bin" -i "${ouput_path}.axf" -o "${ouput_path}.elf" > "${axf2elf_log}"` }); } diff --git a/src/EIDEProject.ts b/src/EIDEProject.ts index d57ba6a9..364d46cb 100644 --- a/src/EIDEProject.ts +++ b/src/EIDEProject.ts @@ -26,6 +26,9 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import * as NodePath from 'path'; import * as events from 'events'; +import * as ini from 'ini'; +import * as os from 'os'; +import * as yaml from 'yaml'; import { File } from '../lib/node-utility/File'; import { FileWatcher } from '../lib/node-utility/FileWatcher'; @@ -50,10 +53,11 @@ import { isNullOrUndefined } from 'util'; import { DeleteDir } from './Platform'; import { IDebugConfigGenerator } from './DebugConfigGenerator'; import { md5, copyObject } from './utility'; -import * as ini from 'ini'; -import * as os from 'os'; import { ResInstaller } from './ResInstaller'; -import { view_str$prompt$not_found_compiler } from './StringTable'; +import { + view_str$prompt$not_found_compiler, view_str$operation$name_can_not_be_blank, + view_str$operation$name_can_not_have_invalid_char +} from './StringTable'; import { SettingManager } from './SettingManager'; export class CheckError extends Error { @@ -121,12 +125,8 @@ export class VirtualSource implements SourceProvider { return path.startsWith(VirtualSource.rootName); } - private getRoot(): VirtualFolder { - return { - name: VirtualSource.rootName, - files: [], - folders: this.config.virtualFolder - }; + public getRoot(): VirtualFolder { + return this.config.virtualFolder; } private getFolderByName(vFolder: VirtualFolder, name: string): VirtualFolder | undefined { @@ -649,6 +649,12 @@ export interface BaseProjectInfo { prjConfig: ProjectConfiguration; } +export interface FilesOptions { + version: string; + files?: { [key: string]: string }; + virtualPathFiles?: { [key: string]: string }; +} + export type DataChangeType = 'pack' | 'dependence' | 'compiler' | 'uploader' | 'files'; export abstract class AbstractProject { @@ -666,8 +672,9 @@ export abstract class AbstractProject { static readonly libFileFilter: RegExp[] = [/\.lib$/i, /\.a$/i, /\.o$/i, /\.obj$/i]; static readonly asmfileFilter: RegExp[] = [/\.s$/i, /\.a51$/i, /\.asm$/i]; - static readonly excludeDirFilter: RegExp[] = [/^\.git$/i, /^\.vs$/i, /^\.vscode$/i, /^\.eide$/i]; + static readonly buildOutputMatcher: RegExp = /\.(?:elf|axf|out|hex|bin|s19|map|map\.view)$/i; + static readonly excludeDirFilter: RegExp[] = [/^\.git$/i, /^\.vs$/i, /^\.vscode$/i, /^\.eide$/i]; // this folder list will be excluded when search include path static readonly excludeIncSearchList: string[] = [DependenceManager.DEPENDENCE_DIR]; @@ -721,6 +728,21 @@ export abstract class AbstractProject { return p1.getWsPath() === p2.getWsPath(); } + static isVirtualSourceGroup(grp: FileGroup): boolean { + return (grp).dir == undefined; + } + + static validateProjectName(value: string): string | undefined { + + if (value.trim() === '') { + return view_str$operation$name_can_not_be_blank; + } + + if (/&|<|>|\(|\)|@|\^|\|/.test(value)) { + return view_str$operation$name_can_not_have_invalid_char; + } + } + private loadProjectDirectory() { // load root folder @@ -849,8 +871,8 @@ export abstract class AbstractProject { return this.sourceRoots.getRootFolderList(); } - getVirtualSourceRootFolders(): VirtualFolder[] { - return this.virtualSource.getFolder()?.folders || []; + getVirtualSourceRoot(): VirtualFolder { + return this.virtualSource.getRoot(); } getVirtualSourceManager(): VirtualSource { @@ -890,7 +912,7 @@ export abstract class AbstractProject { } ToAbsolutePath(path: string): string { - if (!NodePath.isAbsolute(path)) { + if (!File.isAbsolute(path)) { return NodePath.normalize(this.GetRootDir().path + NodePath.sep + path); } return NodePath.normalize(path); @@ -1147,9 +1169,10 @@ export abstract class AbstractProject { this.reloadUploader(oldUploader); } + // @deprecated updateUploaderHexFile(notEmitEvent?: boolean) { // set upload file path if prev value is not existed - const prevBinFile = new File(this.ToAbsolutePath(this.GetConfiguration().uploadConfigModel.getKeyValue('bin'))); + /* const prevBinFile = new File(this.ToAbsolutePath(this.GetConfiguration().uploadConfigModel.getKeyValue('bin'))); if (!prevBinFile.IsFile()) { const binPath = this.getOutputDir() + File.sep + this.GetConfiguration().config.name + '.hex'; if (notEmitEvent) { @@ -1157,7 +1180,7 @@ export abstract class AbstractProject { } else { this.GetConfiguration().uploadConfigModel.SetKeyValue('bin', binPath); } - } + } */ } //-- @@ -1290,9 +1313,9 @@ export abstract class AbstractProject { if (!envFile.IsFile()) { const defTxt: string[] = [ - `###############################################################################################`, - `# project environment config, can be used for 'builder options', 'custom download command'`, - `###############################################################################################`, + `##################################################################################`, + `# project environment config, can be used for 'builder', 'downloader' ...`, + `##################################################################################`, ``, `# mcu ram size`, `#MCU_RAM_SIZE=0x00`, @@ -1336,6 +1359,32 @@ export abstract class AbstractProject { } } + getFilesOptionsFile(): File { + + const target = this.getCurrentTarget().toLowerCase(); + const templateFile = File.fromArray([ + ResManager.GetInstance().GetAppDataDir().path, 'template.files.options.yml' + ]); + + const optFile = File.fromArray([this.getEideDir().path, `${target}.files.options.yml`]); + if (!optFile.IsFile()) { + optFile.Write(templateFile.Read()); + } + + return optFile; + } + + getFilesOptions(): FilesOptions | undefined { + + const optFile = this.getFilesOptionsFile(); + + try { + return yaml.parse(optFile.Read()); + } catch (error) { + GlobalEvent.emit('msg', newMessage('Warning', `error format '${optFile.name}', it must be a yaml file !`)); + } + } + //--- protected loadToolchain() { @@ -1724,27 +1773,28 @@ class EIDEProject extends AbstractProject { // project type is ARM if (cConfig instanceof ArmBaseCompileConfigModel) { + const newDevInfo = this.GetPackManager().getCurrentDevInfo(); + // clear old device if (oldDevice) { const dev = this.packManager.getCurrentDevInfo(oldDevice); const define = dev?.define?.split(/ |,/g); - if (define) { + if (define && newDevInfo) { // if we switch device, remove old device macros this.GetConfiguration().CustomDep_RemoveFromDefineList(define); } } // update new device - const deviceInfo = this.GetPackManager().getCurrentDevInfo(); - if (deviceInfo) { + if (newDevInfo) { // update compile options - cConfig.SetKeyValue('cpuType', deviceInfo.core || 'Cortex-M3'); - cConfig.updateStorageLayout(deviceInfo.storageLayout); + cConfig.SetKeyValue('cpuType', newDevInfo.core || 'Cortex-M3'); + cConfig.updateStorageLayout(newDevInfo.storageLayout); // update device, set macro - prjConfig.config.deviceName = deviceInfo.name; - if (deviceInfo.define) { - this.GetConfiguration().CustomDep_AddAllFromDefineList(deviceInfo.define.split(/ |,/g)); + prjConfig.config.deviceName = newDevInfo.name; + if (newDevInfo.define) { + this.GetConfiguration().CustomDep_AddAllFromDefineList(newDevInfo.define.split(/ |,/g)); } } @@ -1954,6 +2004,14 @@ class EIDEProject extends AbstractProject { settings['C_Cpp.default.cStandard'] = "c99"; } + if (settings['[yaml]'] === undefined) { + settings['[yaml]'] = { + "editor.insertSpaces": true, + "editor.tabSize": 4, + "editor.autoIndent": "advanced" + } + } + if (toolchain.name === 'Keil_C51') { if (settings['C_Cpp.errorSquiggles'] === undefined) { settings['C_Cpp.errorSquiggles'] = "Disabled"; @@ -1965,8 +2023,10 @@ class EIDEProject extends AbstractProject { let recommendExt: string[] = [ "cl.eide", "keroc.hex-fmt", + "xiaoyongdong.srecord", "hars.cppsnippets", "zixuanwang.linkerscript", + "redhat.vscode-yaml" ]; const prjInfo = this.GetConfiguration().config; diff --git a/src/EIDEProjectExplorer.ts b/src/EIDEProjectExplorer.ts index 48571e3b..fd4cdc3a 100644 --- a/src/EIDEProjectExplorer.ts +++ b/src/EIDEProjectExplorer.ts @@ -34,7 +34,7 @@ import { ResManager } from './ResManager'; import { GlobalEvent } from './GlobalEvents'; import { AbstractProject, CheckError, DataChangeType, VirtualSource } from './EIDEProject'; import { ToolchainName, ToolchainManager } from './ToolchainManager'; -import { CreateOptions, PackInfo, ComponentFileItem, DeviceInfo, getComponentKeyDescription, VirtualFolder, VirtualFile, ImportOptions, ProjectTargetInfo, ArmBaseCompileData } from './EIDETypeDefine'; +import { CreateOptions, PackInfo, ComponentFileItem, DeviceInfo, getComponentKeyDescription, VirtualFolder, VirtualFile, ImportOptions, ProjectTargetInfo, ArmBaseCompileData, ProjectConfigData } from './EIDETypeDefine'; import { WorkspaceManager } from './WorkspaceManager'; import { can_not_close_project, project_is_opened, project_load_failed, @@ -59,7 +59,9 @@ import { view_str$settings$prjEnv, view_str$prompt$unresolved_deps, view_str$prompt$prj_location, - view_str$prompt$src_folder_must_be_a_child_of_root + view_str$prompt$src_folder_must_be_a_child_of_root, + view_str$project$folder_type_virtual_desc, + view_str$project$folder_type_fs_desc } from './StringTable'; import { CodeBuilder, BuildOptions } from './CodeBuilder'; import { ExceptionToMessage, newMessage } from './Message'; @@ -68,16 +70,20 @@ import { HexUploaderManager, HexUploaderType } from './HexUploader'; import { Compress, CompressOption } from './Compress'; import { DependenceManager } from './DependenceManager'; import { ArrayDelRepetition } from '../lib/node-utility/Utility'; -import { copyObject, downloadFileWithProgress, getDownloadUrlFromGit, runShellCommand } from './utility'; +import { + copyObject, downloadFileWithProgress, getDownloadUrlFromGit, + runShellCommand, redirectHost, readGithubRepoFolder, FileCache, + genGithubHash +} from './utility'; import { append2SysEnv, DeleteDir, kill } from './Platform'; import { KeilARMOption, KeilC51Option, KeilParser, KeilRteDependence } from './KeilXmlParser'; import { VirtualDocument } from './VirtualDocsProvider'; import { ResInstaller } from './ResInstaller'; import { ExeCmd, ExecutableOption, ExeFile } from '../lib/node-utility/Executable'; import { CmdLineHandler } from './CmdLineHandler'; -import * as CmsisConfigParser from './CmsisConfigParser' -import * as ini from 'ini'; import { WebPanelManager } from './WebPanelManager'; +import * as yml from 'yaml' +import { GitFileInfo } from './WebInterface/GithubInterface'; enum TreeItemType { SOLUTION, @@ -131,6 +137,10 @@ enum TreeItemType { // source refs SRCREF_FILE_ITEM, + // output + OUTPUT_FOLDER, + OUTPUT_FILE_ITEM, + ACTIVED_ITEM, ACTIVED_GROUP } @@ -140,7 +150,7 @@ type GroupRegion = 'PACK' | 'Components' | 'ComponentItem'; interface TreeItemValue { key?: string; alias?: string; - value: string | File; + value: string | File; // if TreeItem refer to a file, the value type is 'File' contextVal?: string; tooltip?: string; icon?: string; @@ -298,11 +308,14 @@ export class ProjTreeItem extends vscode.TreeItem { } } - private getSourceFileIconName(suffix: string): string | undefined { + private getSourceFileIconName(fileName_: string, suffix_: string): string | undefined { let name: string | undefined; - switch (suffix.toLowerCase()) { + const fileName = fileName_.toLowerCase(); + const suffix = suffix_.toLowerCase(); + + switch (suffix) { case '.c': name = 'file_type_c.svg'; break; @@ -331,10 +344,22 @@ export class ProjTreeItem extends vscode.TreeItem { break; case '.o': case '.obj': + case '.axf': + case '.elf': + case '.bin': + case '.out': name = 'file_type_binary.svg'; break; + case '.map': + name = 'file_type_map.svg'; + break; + // other suffix default: - name = 'document-light.svg'; + if (fileName.endsWith('.map.view')) { + name = 'Report_16x.svg'; + } else { + name = 'document-light.svg'; + } break; } @@ -392,9 +417,11 @@ export class ProjTreeItem extends vscode.TreeItem { name = 'TransferDownload_16x.svg'; break; case TreeItemType.DEPENDENCE_GROUP: - case TreeItemType.DEPENDENCE: name = 'DependencyGraph_16x.svg'; break; + case TreeItemType.DEPENDENCE: + name = 'Property_16x.svg'; + break; case TreeItemType.SETTINGS: name = 'Settings_16x.svg'; break; @@ -407,6 +434,9 @@ export class ProjTreeItem extends vscode.TreeItem { case TreeItemType.ACTIVED_GROUP: name = 'TestCoveredPassing_16x.svg';//'RecursivelyCheckAll_16x.svg'; break; + case TreeItemType.OUTPUT_FOLDER: + name = 'folder_type_binary.svg'; + break; default: { // if it's a source file, get icon @@ -414,7 +444,7 @@ export class ProjTreeItem extends vscode.TreeItem { const file: File = this.val.value; // if file is existed, get icon by suffix if (file.IsFile()) { - name = this.getSourceFileIconName(file.suffix); + name = this.getSourceFileIconName(file.name, file.suffix); } // if file not existed, show warning icon else { @@ -445,8 +475,48 @@ interface VirtualFolderInfo { } interface VirtualFileInfo { - path: string; - vFile: VirtualFile; + path: string; // virtual path + vFile: VirtualFile; // virtual file info +} + +class ProjectItemCache { + + // + private itemCache: Map = new Map(); + + getTreeItem(prj: AbstractProject, itemType: TreeItemType): ProjTreeItem | undefined { + const cache = this.itemCache.get(prj.getWsPath()); + if (cache) { + return cache[TreeItemType[itemType]]; + } + } + + setTreeItem(prj: AbstractProject, item: ProjTreeItem, isRoot?: boolean) { + const cache = this.itemCache.get(prj.getWsPath()); + if (cache) { + if (isRoot) { + cache.root = item; + } else { + cache[TreeItemType[item.type]] = item; + } + } else if (isRoot) { // if not found and type is root, set it + this.itemCache.set(prj.getWsPath(), { root: item }); + } + } + + delTreeItem(prj: AbstractProject, itemType?: TreeItemType): ProjTreeItem | undefined { + const cache = this.itemCache.get(prj.getWsPath()); + if (cache) { + if (itemType) { + const key = TreeItemType[itemType]; + const deleted = cache[key]; + cache[key] = undefined; // del item + return deleted; + } else { // del all + this.itemCache.delete(prj.getWsPath()); + } + } + } } class ProjectDataProvider implements vscode.TreeDataProvider { @@ -459,7 +529,9 @@ class ProjectDataProvider implements vscode.TreeDataProvider { private recFile: File; private context: vscode.ExtensionContext; private activePrjPath: string | undefined; - private itemCache: Map = new Map(); + + // project tree item refresh cache + treeCache: ProjectItemCache = new ProjectItemCache(); onDidChangeTreeData?: vscode.Event | undefined; dataChangedEvent: vscode.EventEmitter; @@ -483,30 +555,23 @@ class ProjectDataProvider implements vscode.TreeDataProvider { this.LoadWorkspaceProject(); } - getCacheItem(prj: AbstractProject, itemType: TreeItemType): ProjTreeItem | undefined { - const cache = this.itemCache.get(prj.getWsPath()); - if (cache) { - return cache[TreeItemType[itemType]]; - } - } - onProjectChanged(prj: AbstractProject, type?: DataChangeType) { switch (type) { case 'files': - this.UpdateView(this.getCacheItem(prj, TreeItemType.PROJECT)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.PROJECT)); break; case 'compiler': - this.UpdateView(this.getCacheItem(prj, TreeItemType.COMPILE_CONFIGURATION)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.COMPILE_CONFIGURATION)); break; case 'uploader': - this.UpdateView(this.getCacheItem(prj, TreeItemType.UPLOAD_OPTION)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.UPLOAD_OPTION)); break; case 'pack': - this.UpdateView(this.getCacheItem(prj, TreeItemType.PACK)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.PACK)); break; case 'dependence': - this.UpdateView(this.getCacheItem(prj, TreeItemType.PACK)); - this.UpdateView(this.getCacheItem(prj, TreeItemType.DEPENDENCE)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.PACK)); + this.UpdateView(this.treeCache.getTreeItem(prj, TreeItemType.DEPENDENCE)); break; default: this.UpdateView(); @@ -647,13 +712,8 @@ class ProjectDataProvider implements vscode.TreeDataProvider { }); iList.push(cItem); - // cache item - const oldItem = this.itemCache.get(sln.getWsPath()); - if (oldItem) { - oldItem.root = cItem; - } else { - this.itemCache.set(sln.getWsPath(), { root: cItem }); - } + // cache project root item + this.treeCache.setTreeItem(sln, cItem, true); }); } else { @@ -666,7 +726,8 @@ class ProjectDataProvider implements vscode.TreeDataProvider { iList.push(new ProjTreeItem(TreeItemType.PROJECT, { value: view_str$project$title, projectIndex: element.val.projectIndex, - tooltip: view_str$project$title + tooltip: view_str$project$title, + obj: { path: VirtualSource.rootName, vFolder: project.getVirtualSourceRoot() } })); if (prjType === 'ARM') { // only display for ARM project @@ -704,45 +765,80 @@ class ProjectDataProvider implements vscode.TreeDataProvider { })); // cache sub root view - const iCache = this.itemCache.get(project.getWsPath()); - if (iCache) { - iList.forEach((item) => { iCache[TreeItemType[item.type]] = item; }); - } + iList.forEach((item) => { + this.treeCache.setTreeItem(project, item); + }); } break; case TreeItemType.PROJECT: - // push filesystem source folder - project.getSourceRootFolders() - .sort((info_1, info_2) => { - const isComponent = info_1.displayName === DependenceManager.DEPENDENCE_DIR; - return isComponent ? -1 : info_1.displayName.localeCompare(info_2.displayName); - }) - .forEach((rootInfo) => { - const isComponent = rootInfo.displayName === DependenceManager.DEPENDENCE_DIR; - const folderDispName = isComponent ? view_str$project$cmsis_components : rootInfo.displayName; - iList.push(new ProjTreeItem(TreeItemType.FOLDER_ROOT, { - value: folderDispName, - obj: rootInfo.fileWatcher.file, - projectIndex: element.val.projectIndex, - contextVal: isComponent ? 'FOLDER_ROOT_DEPS' : undefined, - tooltip: rootInfo.needUpdate ? view_str$project$needRefresh : folderDispName, - icon: rootInfo.needUpdate ? - 'StatusWarning_16x.svg' : (isComponent ? 'DependencyGraph_16x.svg' : undefined) - })); - }); - // push virtual source folder - project.getVirtualSourceRootFolders() - .sort((folder1, folder2) => { return folder1.name.localeCompare(folder2.name); }) - .forEach((vFolder) => { - const vFolderPath = `${VirtualSource.rootName}/${vFolder.name}`; - const itemType = project.isExcluded(vFolderPath) ? TreeItemType.V_EXCFOLDER : TreeItemType.V_FOLDER_ROOT; - iList.push(new ProjTreeItem(itemType, { - value: vFolder.name, - obj: { path: vFolderPath, vFolder: vFolder }, + { + // push filesystem source folder + project.getSourceRootFolders() + .sort((info_1, info_2) => { + const isComponent = info_1.displayName === DependenceManager.DEPENDENCE_DIR; + return isComponent ? -1 : info_1.displayName.localeCompare(info_2.displayName); + }) + .forEach((rootInfo) => { + const isComponent = rootInfo.displayName === DependenceManager.DEPENDENCE_DIR; + const folderDispName = isComponent ? view_str$project$cmsis_components : rootInfo.displayName; + iList.push(new ProjTreeItem(TreeItemType.FOLDER_ROOT, { + value: folderDispName, + obj: rootInfo.fileWatcher.file, + projectIndex: element.val.projectIndex, + contextVal: isComponent ? 'FOLDER_ROOT_DEPS' : undefined, + tooltip: rootInfo.needUpdate ? view_str$project$needRefresh : folderDispName, + icon: rootInfo.needUpdate ? + 'StatusWarning_16x.svg' : (isComponent ? 'DependencyGraph_16x.svg' : undefined) + })); + }); + + // push virtual source folder + project.getVirtualSourceRoot().folders + .sort((folder1, folder2) => { return folder1.name.localeCompare(folder2.name); }) + .forEach((vFolder) => { + const vFolderPath = `${VirtualSource.rootName}/${vFolder.name}`; + const itemType = project.isExcluded(vFolderPath) ? TreeItemType.V_EXCFOLDER : TreeItemType.V_FOLDER_ROOT; + iList.push(new ProjTreeItem(itemType, { + value: vFolder.name, + obj: { path: vFolderPath, vFolder: vFolder }, + projectIndex: element.val.projectIndex, + tooltip: `${vFolder.name} (${vFolder.files.length} files, ${vFolder.folders.length} folders)` + })); + }); + + // put virtual source files + project.getVirtualSourceRoot().files + .sort((a, b) => a.path.localeCompare(b.path)) + .forEach((vFile) => { + const file = new File(project.ToAbsolutePath(vFile.path)); + const vFilePath = `${VirtualSource.rootName}/${file.name}`; + const isFileExcluded = project.isExcluded(vFilePath); + const itemType = isFileExcluded ? TreeItemType.V_EXCFILE_ITEM : TreeItemType.V_FILE_ITEM; + iList.push(new ProjTreeItem(itemType, { + value: file, + collapsibleState: project.getSourceRefs(file).length > 0 ? + vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, + obj: { path: vFilePath, vFile: vFile }, + projectIndex: element.val.projectIndex, + tooltip: isFileExcluded ? view_str$project$excludeFile : file.path, + })); + }); + + // show output files + if (SettingManager.GetInstance().isShowOutputFilesInExplorer()) { + const label = `Output Files`; + const tItem = new ProjTreeItem(TreeItemType.OUTPUT_FOLDER, { + value: label, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, projectIndex: element.val.projectIndex, - tooltip: vFolder.name - })); - }); + tooltip: label, + }); + iList.push(tItem); + this.treeCache.setTreeItem(project, tItem); + } else { + this.treeCache.delTreeItem(project, TreeItemType.OUTPUT_FOLDER); + } + } break; case TreeItemType.PACK: { @@ -927,33 +1023,39 @@ class ProjectDataProvider implements vscode.TreeDataProvider { const curFolder = element.val.obj; // put child folders - curFolder.vFolder.folders.forEach((vFolder) => { - const vFolderPath = `${curFolder.path}/${vFolder.name}`; - const isFolderExcluded = project.isExcluded(vFolderPath); - const itemType = isFolderExcluded ? TreeItemType.V_EXCFOLDER : TreeItemType.V_FOLDER; - iList.push(new ProjTreeItem(itemType, { - value: vFolder.name, - obj: { path: vFolderPath, vFolder: vFolder }, - projectIndex: element.val.projectIndex, - tooltip: isFolderExcluded ? view_str$project$excludeFolder : vFolder.name, - })); - }); + curFolder.vFolder.folders + .sort((a, b) => a.name.localeCompare(b.name)) + .forEach((vFolder) => { + const vFolderPath = `${curFolder.path}/${vFolder.name}`; + const isFolderExcluded = project.isExcluded(vFolderPath); + const itemType = isFolderExcluded ? TreeItemType.V_EXCFOLDER : TreeItemType.V_FOLDER; + iList.push(new ProjTreeItem(itemType, { + value: vFolder.name, + obj: { path: vFolderPath, vFolder: vFolder }, + projectIndex: element.val.projectIndex, + tooltip: isFolderExcluded + ? view_str$project$excludeFolder + : `${vFolder.name} (${vFolder.files.length} files, ${vFolder.folders.length} folders)`, + })); + }); // put child files - curFolder.vFolder.files.forEach((vFile) => { - const file = new File(project.ToAbsolutePath(vFile.path)); - const vFilePath = `${curFolder.path}/${file.name}`; - const isFileExcluded = project.isExcluded(vFilePath); - const itemType = isFileExcluded ? TreeItemType.V_EXCFILE_ITEM : TreeItemType.V_FILE_ITEM; - iList.push(new ProjTreeItem(itemType, { - value: file, - collapsibleState: project.getSourceRefs(file).length > 0 ? - vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, - obj: { path: vFilePath, vFile: vFile }, - projectIndex: element.val.projectIndex, - tooltip: isFileExcluded ? view_str$project$excludeFile : file.path, - })); - }); + curFolder.vFolder.files + .sort((a, b) => a.path.localeCompare(b.path)) + .forEach((vFile) => { + const file = new File(project.ToAbsolutePath(vFile.path)); + const vFilePath = `${curFolder.path}/${file.name}`; + const isFileExcluded = project.isExcluded(vFilePath); + const itemType = isFileExcluded ? TreeItemType.V_EXCFILE_ITEM : TreeItemType.V_FILE_ITEM; + iList.push(new ProjTreeItem(itemType, { + value: file, + collapsibleState: project.getSourceRefs(file).length > 0 ? + vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, + obj: { path: vFilePath, vFile: vFile }, + projectIndex: element.val.projectIndex, + tooltip: isFileExcluded ? view_str$project$excludeFile : file.path, + })); + }); } break; case TreeItemType.EXCFOLDER: @@ -978,6 +1080,26 @@ class ProjectDataProvider implements vscode.TreeDataProvider { } } break; + // output folder + case TreeItemType.OUTPUT_FOLDER: + { + const outFolder = project.getOutputFolder(); + if (outFolder.IsDir()) { + const fList = outFolder.GetList([AbstractProject.buildOutputMatcher], File.EMPTY_FILTER); + fList.forEach((file) => { + iList.push(new ProjTreeItem(TreeItemType.OUTPUT_FILE_ITEM, { + value: file, + collapsibleState: vscode.TreeItemCollapsibleState.None, + projectIndex: element.val.projectIndex, + tooltip: file.path, + })); + }); + } + } + break; + // output file item + case TreeItemType.OUTPUT_FILE_ITEM: + break; case TreeItemType.COMPONENT_GROUP: case TreeItemType.ACTIVED_GROUP: case TreeItemType.PACK_GROUP: @@ -1257,7 +1379,11 @@ class ProjectDataProvider implements vscode.TreeDataProvider { const projectInfo = baseInfo.prjConfig.config; // init project info - projectInfo.virtualFolder = []; + projectInfo.virtualFolder = { + name: VirtualSource.rootName, + files: [], + folders: [] + }; const getVirtualFolder = (path: string, noCreate?: boolean): VirtualFolder | undefined => { @@ -1268,10 +1394,8 @@ class ProjectDataProvider implements vscode.TreeDataProvider { const pathList = path.split('/'); pathList.splice(0, 1); // remvoe root - let curFolder: VirtualFolder = { - name: VirtualSource.rootName, files: [], - folders: projectInfo.virtualFolder - }; + // init start search folder + let curFolder: VirtualFolder = projectInfo.virtualFolder; for (const name of pathList) { const index = curFolder.folders.findIndex((folder) => { return folder.name === name; }); @@ -1311,51 +1435,53 @@ class ProjectDataProvider implements vscode.TreeDataProvider { const incs: string[] = this.importCmsisHeaders(baseInfo.rootFolder).map((f) => f.path); /* try resolve all deps */ - const mdkRoot = SettingManager.GetInstance().GetMdkDir(); - const fileTypes: string[] = ['source', 'header']; - rte_deps.forEach((dep) => { - /* check dep whether is valid */ - if (fileTypes.includes(dep.category || '') && dep.class && dep.packPath) { - const srcFileLi: File[] = []; - const vFolder = getVirtualFolder(`${VirtualSource.rootName}/::${dep.class}`, true); - - /* add all candidate files */ - if (dep.instance) { srcFileLi.push(new File(dep.instance[0])) } - srcFileLi.push(File.fromArray([mdkRoot.path, 'PACK', dep.packPath, dep.path])); - - /* resolve dependences */ - for (const srcFile of srcFileLi) { - - /* check condition */ - if (!srcFile.IsFile()) { continue; } - if (dep.category == 'source' && !vFolder) { continue; } - - let srcRePath: string | undefined = baseInfo.rootFolder.ToRelativePath(srcFile.path, false); - - /* if it's not in workspace, copy it */ - if (srcRePath == undefined) { - srcRePath = ['.cmsis', dep.packPath, dep.path].join(File.sep); - const realFolder = File.fromArray([baseInfo.rootFolder.path, NodePath.dirname(srcRePath)]); - realFolder.CreateDir(true); - realFolder.CopyFile(srcFile); - } + const mdkRoot = SettingManager.GetInstance().GetMdkArmDir(); + if (mdkRoot) { // MDK ARM dir, like: 'D:\keil\ARM' + const fileTypes: string[] = ['source', 'header']; + rte_deps.forEach((dep) => { + /* check dep whether is valid */ + if (fileTypes.includes(dep.category || '') && dep.class && dep.packPath) { + const srcFileLi: File[] = []; + const vFolder = getVirtualFolder(`${VirtualSource.rootName}/::${dep.class}`, true); + + /* add all candidate files */ + if (dep.instance) { srcFileLi.push(new File(dep.instance[0])) } + srcFileLi.push(File.fromArray([mdkRoot.path, 'PACK', dep.packPath, dep.path])); + + /* resolve dependences */ + for (const srcFile of srcFileLi) { + + /* check condition */ + if (!srcFile.IsFile()) { continue; } + if (dep.category == 'source' && !vFolder) { continue; } + + let srcRePath: string | undefined = baseInfo.rootFolder.ToRelativePath(srcFile.path, false); + + /* if it's not in workspace, copy it */ + if (srcRePath == undefined) { + srcRePath = ['.cmsis', dep.packPath, dep.path].join(File.sep); + const realFolder = File.fromArray([baseInfo.rootFolder.path, NodePath.dirname(srcRePath)]); + realFolder.CreateDir(true); + realFolder.CopyFile(srcFile); + } - /* if it's a source, add to project */ - if (dep.category == 'source' && vFolder) { - vFolder.files.push({ path: srcRePath }); - } + /* if it's a source, add to project */ + if (dep.category == 'source' && vFolder) { + vFolder.files.push({ path: srcRePath }); + } - /* if it's a header, add to include path */ - else if (dep.category == 'header') { - incs.push(`${baseInfo.rootFolder.path}${File.sep}${NodePath.dirname(srcRePath)}`); - } + /* if it's a header, add to include path */ + else if (dep.category == 'header') { + incs.push(`${baseInfo.rootFolder.path}${File.sep}${NodePath.dirname(srcRePath)}`); + } - return; /* resolved !, exit */ + return; /* resolved !, exit */ + } } - } - /* resolve failed !, store dep */ - unresolved_deps.push(dep); - }); + /* resolve failed !, store dep */ + unresolved_deps.push(dep); + }); + } /* add include paths for targets */ const mdk_rte_folder = File.fromArray([`${keilPrjFile.dir}`, 'RTE']); @@ -1366,26 +1492,38 @@ class ProjectDataProvider implements vscode.TreeDataProvider { /* log unresolved deps */ if (unresolved_deps.length > 0) { + + const title = `!!! ${WARNING} !!!`; + const lines: string[] = [ - `!!! ${WARNING} !!!`, + `${title}`, view_str$prompt$unresolved_deps, view_str$prompt$prj_location.replace('{}', baseInfo.workspaceFile.path), '---' ]; + unresolved_deps.forEach((dep) => { - const nLine: string[] = []; + let locate = dep.packPath; - if (dep.instance) { locate = baseInfo.rootFolder.ToRelativePath(dep.instance[0], false) || dep.instance[0] } - nLine.push( + if (dep.instance) { + locate = baseInfo.rootFolder + .ToRelativePath(dep.instance[0], false) || dep.instance[0] + } + + const nLine: string[] = [ `FileName: '${dep.path}'`, `\tClass: '${dep.class}'`, `\tCategory: '${dep.category}'`, `\tLocation: '${locate}'`, - ); + ]; + lines.push(nLine.join(os.EOL)); }); + const cont = lines.join(`${os.EOL}${os.EOL}`); - const doc = await vscode.workspace.openTextDocument({ content: cont }); + const file = File.fromArray([baseInfo.rootFolder.path, `${title}.txt`]); + file.Write(cont); // write content to file + const doc = await vscode.workspace.openTextDocument(vscode.Uri.parse(file.ToUri())); vscode.window.showTextDocument(doc, { preview: false }); } } @@ -1512,6 +1650,20 @@ class ProjectDataProvider implements vscode.TreeDataProvider { // convert .EIDE to .eide this.toLowercaseEIDEFolder(targetDir); + // rename project name + { + const prjFile = File.fromArray([targetDir.path, AbstractProject.EIDE_DIR, AbstractProject.prjConfigName]); + if (!prjFile.IsFile()) throw Error(`project file: '${prjFile.path}' is not exist !`); + + try { + const prjConf: ProjectConfigData = JSON.parse(prjFile.Read()); + prjConf.name = option.name; // set project name + prjFile.Write(JSON.stringify(prjConf)); + } catch (error) { + throw Error(`change project name failed !, msg: ${error.message}`); + } + } + // switch workspace if user select `yes` const item = await vscode.window.showInformationMessage( view_str$operation$create_prj_done, 'Yes', 'Later' @@ -1536,33 +1688,6 @@ class ProjectDataProvider implements vscode.TreeDataProvider { } } - async InstallKeilPackage(prjIndex: number, reporter?: (progress?: number, message?: string) => void) { - - if (this.prjList[prjIndex].GetPackManager().GetPack()) { - GlobalEvent.emit('msg', { - type: 'Warning', - contentType: 'string', - content: 'You should uninstall old package before install a new one !' - }); - return; - } - - const packFile = await vscode.window.showOpenDialog({ - canSelectFolders: false, - canSelectFiles: true, - openLabel: install_this_pack, - filters: { - 'CMSIS Package': ['pack'] - } - }); - - if (packFile === undefined) { - return; - } - - return this.prjList[prjIndex].InstallPack(new File(packFile[0].fsPath), reporter); - } - async UninstallKeilPackage(item: ProjTreeItem) { const prj = this.prjList[item.val.projectIndex]; if (prj.GetPackManager().GetPack()) { @@ -1636,7 +1761,7 @@ class ProjectDataProvider implements vscode.TreeDataProvider { return { label: dev.name, description: dev.core }; }); const item = await vscode.window.showQuickPick(devList, { - placeHolder: 'found ' + devList.length + ' devices, ' + set_device_hint, + placeHolder: 'Found ' + devList.length + ' devices, ' + set_device_hint, canPickMany: false, matchOnDescription: true }); @@ -1690,8 +1815,18 @@ interface BuildCommandInfo { order?: number; } +interface ImporterProjectInfo { + name: string; + target?: string; + incList: string[]; + defineList: string[]; + files: VirtualFolder; +} + export class ProjectExplorer { + private readonly vFolderNameMatcher = /^\w[\w\t \-:@\.]*$/; + private view: vscode.TreeView; private dataProvider: ProjectDataProvider; @@ -1714,13 +1849,15 @@ export class ProjectExplorer { // create cppcheck output channel this.cppcheck_out = vscode.window.createOutputChannel('eide-cppcheck'); - // when file saved, clear diagnostic - /* vscode.workspace.onDidSaveTextDocument((document) => { - const uri = document.uri; - if (this.cppcheck_diag.has(uri)) { - this.cppcheck_diag.delete(uri); - } - }); */ + // register doc event + context.subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc) => { + this.onCustomDepYamlSaved(doc); + this.onFolderSourceYamlSaved(doc); + })); + context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((doc) => { + this.onCustomDepYamlClosed(doc); + this.onFolderSourceYamlClosed(doc); + })); this.on('request_open_project', (fsPath: string) => this.dataProvider.OpenProject(fsPath)); this.on('request_create_project', (option: CreateOptions) => this.dataProvider.CreateProject(option)); @@ -1770,10 +1907,11 @@ export class ProjectExplorer { private async createTarget(prj: AbstractProject) { let targetName = await vscode.window.showInputBox({ - placeHolder: 'input a target name', + placeHolder: 'Input a target name', + ignoreFocusOut: true, validateInput: (val: string) => { if (val.length > 25) { return `string is too long !, length must < 25, current is ${val.length}`; } - if (!/^[\w-]+$/.test(val)) { return `string can only contain word, number or '_' !`; } + if (!/^[\w\-]+$/.test(val)) { return `string can only contain word, number '-' or '_' !`; } return undefined; } }); @@ -1791,7 +1929,7 @@ export class ProjectExplorer { private async deleteTarget(prj: AbstractProject) { - const selTarget = await vscode.window.showQuickPick(prj.getTargets(), { placeHolder: 'select a target to delete' }); + const selTarget = await vscode.window.showQuickPick(prj.getTargets(), { placeHolder: 'Select a target to delete' }); if (selTarget === undefined) { return; } const curTarget = prj.getCurrentTarget(); @@ -1891,7 +2029,7 @@ export class ProjectExplorer { vscode.window.showQuickPick(records.reverse(), { canPickMany: false, - placeHolder: `found ${records.length} results, select one to open`, + placeHolder: `Found ${records.length} results, select one to open`, matchOnDescription: false, matchOnDetail: true, ignoreFocusOut: false @@ -1910,6 +2048,13 @@ export class ProjectExplorer { this.dataProvider.clearAllRecords(); } + notifyUpdateOutputFolder(prj: AbstractProject) { + const item = this.dataProvider.treeCache.getTreeItem(prj, TreeItemType.OUTPUT_FOLDER); + if (item) { + this.dataProvider.UpdateView(item); + } + } + private _buildLock: boolean = false; BuildSolution(prjItem?: ProjTreeItem, options?: BuildOptions) { @@ -1927,10 +2072,6 @@ export class ProjectExplorer { this._buildLock = true; - vscode.window.visibleTextEditors.forEach(editor => { - editor.document.save(); - }); - // save project before build prj.Save(); @@ -1939,7 +2080,12 @@ export class ProjectExplorer { // set event handler const toolchain = prj.getToolchain().name; - codeBuilder.on('finished', () => prj.notifyUpdateSourceRefs(toolchain)); + + // notify view update after build done ! + codeBuilder.on('finished', () => { + prj.notifyUpdateSourceRefs(toolchain); + this.notifyUpdateOutputFolder(prj); + }); // start build codeBuilder.build(options); @@ -1966,9 +2112,6 @@ export class ProjectExplorer { return; } - /* save all editor */ - vscode.window.visibleTextEditors.forEach(editor => { editor.document.save(); }); - const cmdList: BuildCommandInfo[] = []; this.dataProvider.foreachProject((project, index) => { @@ -2035,7 +2178,7 @@ export class ProjectExplorer { } private _uploadLock: boolean = false; - async UploadToDevice(prjItem?: ProjTreeItem) { + async UploadToDevice(prjItem?: ProjTreeItem, eraseAll?: boolean) { const prj = this.getProjectByTreeItem(prjItem); @@ -2053,7 +2196,7 @@ export class ProjectExplorer { const uploader = HexUploaderManager.getInstance().createUploader(prj); try { - await uploader.upload(); + await uploader.upload(eraseAll); } catch (error) { GlobalEvent.emit('error', error); } @@ -2109,30 +2252,205 @@ export class ProjectExplorer { return; } + if (prj.GetPackManager().GetPack()) { + GlobalEvent.emit('msg', { + type: 'Warning', + contentType: 'string', + content: 'You should uninstall old package before install a new one !' + }); + this.installLocked = false; + return; + } + vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: `Installing CMSIS package` + title: `Installing cmsis package` }, (progress) => { - return new Promise(async (resolve) => { + return new Promise(async (resolve_) => { + + const resolve = () => { + this.installLocked = false; + resolve_(); + }; + try { progress.report({ message: 'preparing ...' }); - await this.dataProvider.InstallKeilPackage(prjIndex, (_progress, msg) => { + let packFile: File; + + const insType = await vscode.window.showQuickPick([ + { + label: 'From Repo', + detail: 'Download cmsis pack from the repository and install' + }, + { + label: 'From Disk', + detail: 'Select cmsis pack file from your computer and install' + } + ], { + placeHolder: `Select an installation type. Press 'Esc' to exit`, + canPickMany: false, + ignoreFocusOut: true + }); + + if (insType === undefined) { // canceled, exit + resolve(); + return; + } + + // download from internet + if (insType.label == 'From Repo') { + + progress.report({ message: 'waiting download task done ...' }); + + const res = await this.startDownloadCmsisPack(); + + if (res === undefined) { // canceled, exit + resolve(); + return; + } + + if (res instanceof Error) { + GlobalEvent.emit('msg', ExceptionToMessage(res, 'Warning')); + resolve(); + return; + } + + packFile = res; + } + + // from disk + else { + const urls = await vscode.window.showOpenDialog({ + canSelectFolders: false, + canSelectFiles: true, + openLabel: install_this_pack, + filters: { + 'Cmsis Package': ['pack'] + } + }); + + if (urls === undefined) { // canceled, exit + resolve(); + return; + } + + packFile = new File(urls[0].fsPath); + } + + await prj.InstallPack(packFile, (_progress, msg) => { progress.report({ increment: _progress ? 12 : undefined, message: msg }); }); + + resolve(); + } catch (error) { GlobalEvent.emit('msg', ExceptionToMessage(error, 'Warning')); + resolve(); } - this.installLocked = false; - resolve(); }); }); } + private async startDownloadCmsisPack(): Promise { + + const redirectUri = (uri: string) => { + return SettingManager.GetInstance().isUseGithubProxy() ? redirectHost(uri) : uri; + }; + + // URL: https://api.github.com/repos/github0null/eide-cmsis-pack/contents/packages + const repoUrl = redirectUri('api.github.com/repos/' + SettingManager.GetInstance().getCmsisPackRepositoryUrl()); + + return await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Download cmsis package` + }, async (progress, cancelToken) => { + + progress.report({ message: `reading package list ...` }); + + const pkgList = await readGithubRepoFolder(repoUrl); + if (pkgList instanceof Error) { + return pkgList; + } + + progress.report({ message: `waiting cmsis package selection ...` }); + + const itemList: vscode.QuickPickItem[] = pkgList + .filter((inf) => inf.type == 'file') + .map((fileInfo) => { + return { + label: fileInfo.name, + detail: `Size: ${(fileInfo.size / 1000000).toFixed(1)} MB, Sha: ${fileInfo.sha}`, + val: fileInfo + }; + }); + + const item: any = await vscode.window.showQuickPick(itemList, { + placeHolder: `Found ${pkgList.length} packages, select one to install. Press 'Esc' to exit`, + canPickMany: false, + ignoreFocusOut: true, + matchOnDescription: true + }); + + if (item == undefined) { // user canceled + return undefined; + } + + try { + + const gitFileInfo: GitFileInfo = item.val; + let packageFile: File | undefined; + + const resManager = ResManager.GetInstance(); + const packDir = File.fromArray([resManager.getEideHomeFolder().path, 'pack', 'cmsis']); + packDir.CreateDir(true); + + // read cache + const cache = new FileCache(packDir); + packageFile = cache.get(gitFileInfo.name, gitFileInfo.sha); + if (packageFile) { // found cache, use it + return packageFile; + } + + // download it + progress.report({ message: `initializing download '${gitFileInfo.name}' ...` }); + + if (gitFileInfo.download_url == undefined) { + return new Error(`Can't download '${gitFileInfo.name}', not download url found !`); + } + + const url = redirectUri(gitFileInfo.download_url); + const buff = await downloadFileWithProgress(url, gitFileInfo.name, progress, cancelToken); + + if (buff == undefined) { // canceled + return undefined; + } + + if (buff instanceof Error) { + return buff; + } + + // save file + packageFile = File.fromArray([packDir.path, gitFileInfo.name]); + fs.writeFileSync(packageFile.path, buff); + + // add to cache + const sha = genGithubHash(buff); + cache.add(packageFile.name, sha); + cache.save(); + + return packageFile; + + } catch (error) { + return error; + } + }); + } + private exportLocked: boolean = false; async ExportToTemplate(prjItem?: ProjTreeItem, isWorkspace?: boolean) { @@ -2327,16 +2645,27 @@ export class ProjectExplorer { const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); - const folderType = await vscode.window.showQuickPick( - [view_str$project$folder_type_fs, view_str$project$folder_type_virtual], - { placeHolder: view_str$project$sel_folder_type }); + const folderType = await vscode.window.showQuickPick( + [ + { + label: view_str$project$folder_type_virtual, + detail: view_str$project$folder_type_virtual_desc + }, + { + label: view_str$project$folder_type_fs, + detail: view_str$project$folder_type_fs_desc + } + ], + { + placeHolder: view_str$project$sel_folder_type + }); if (folderType === undefined) { return; } // add folder from filesystem - if (folderType === view_str$project$folder_type_fs) { + if (folderType.label === view_str$project$folder_type_fs) { const folderList = await vscode.window.showOpenDialog({ canSelectMany: true, @@ -2371,15 +2700,16 @@ export class ProjectExplorer { } } - // add virtual folder + // add root virtual folder else { const folderName = await vscode.window.showInputBox({ - placeHolder: 'Input folder name', + placeHolder: 'Input a folder name', ignoreFocusOut: true, validateInput: (input) => { - if (!/^\w+$/.test(input)) { return `must match '^\\w+$'`; } - return undefined; + if (!this.vFolderNameMatcher.test(input)) { + return `must match '${this.vFolderNameMatcher.source}'`; + } } }); @@ -2403,6 +2733,9 @@ export class ProjectExplorer { const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); switch (item.type) { + case TreeItemType.OUTPUT_FOLDER: + this.notifyUpdateOutputFolder(prj); + break; case TreeItemType.V_FOLDER_ROOT: prj.refreshSourceRoot((item.val.obj).path); break; @@ -2457,11 +2790,12 @@ export class ProjectExplorer { const curFolder = item.val.obj; const folderName = await vscode.window.showInputBox({ - placeHolder: 'Input folder name', + placeHolder: 'Input a folder name', ignoreFocusOut: true, validateInput: (input) => { - if (!/^\w+$/.test(input)) { return `must match '^\\w+$'`; } - return undefined; + if (!this.vFolderNameMatcher.test(input)) { + return `must match '${this.vFolderNameMatcher.source}'`; + } } }); @@ -2482,10 +2816,11 @@ export class ProjectExplorer { const curFolder = item.val.obj; const folderName = await vscode.window.showInputBox({ - placeHolder: 'Input the new name', + prompt: 'Input the new name', ignoreFocusOut: true, + value: curFolder.vFolder.name, validateInput: (input) => { - if (!/^\w+$/.test(input)) { return `must match '^\\w+$'`; } + if (!this.vFolderNameMatcher.test(input)) { return `must match '${this.vFolderNameMatcher.source}'`; } return undefined; } }); @@ -2542,39 +2877,61 @@ export class ProjectExplorer { async showDisassembly(uri: vscode.Uri) { + const supportList = ['RISCV_GCC', 'GCC', 'AC5', 'AC6']; + try { - /* parser ref json */ + // check condition const activePrj = this.dataProvider.getActiveProject(); if (!activePrj) { throw new Error('Not found active project !'); } const toolchainName = activePrj.getToolchain().name; - if (!['RISCV_GCC', 'GCC'].includes(toolchainName)) { throw new Error('Only support GCC compiler !'); } + if (!supportList.includes(toolchainName)) { + throw new Error(`Only support '${supportList.join(',')}' compiler !`); + } + + // parser ref json const srcPath = uri.fsPath; const refFile = File.fromArray([activePrj.ToAbsolutePath(activePrj.getOutputDir()), 'ref.json']); if (!refFile.IsFile()) { throw new Error(`Not found 'ref.json' at output folder, you need build project !`) } const ref = JSON.parse(refFile.Read()); if (!ref[srcPath]) { throw new Error(`Not found any reference for this source file !`) } - /* get obj file */ + // get obj file let objPath: string | undefined; - - if (typeof ref[srcPath] == 'string') { - objPath = ref[srcPath]; - } else if (Array.isArray(ref[srcPath])) { - objPath = ref[srcPath][0]; - } - + if (typeof ref[srcPath] == 'string') { objPath = ref[srcPath]; } + else if (Array.isArray(ref[srcPath])) { objPath = ref[srcPath][0]; } if (objPath == undefined) { throw new Error(`Not found any reference for this source file !`) } - /* get objdump.exe */ - const toolPrefix = toolchainName == 'GCC' ? - SettingManager.GetInstance().getGCCPrefix() : - SettingManager.GetInstance().getRiscvToolPrefix(); - const exeFile = File.fromArray([activePrj.getToolchain().getToolchainDir().path, 'bin', `${toolPrefix}objdump.exe`]); - if (!exeFile.IsFile()) { throw Error(`Not found '${exeFile.name}' !`) } + // prepare command + let exeFile: File; + let cmds: string[]; + + switch (toolchainName) { + case 'GCC': + case 'RISCV_GCC': + { + const toolPrefix = toolchainName == 'GCC' ? + SettingManager.GetInstance().getGCCPrefix() : + SettingManager.GetInstance().getRiscvToolPrefix(); + exeFile = File.fromArray([activePrj.getToolchain().getToolchainDir().path, 'bin', `${toolPrefix}objdump.exe`]); + if (!exeFile.IsFile()) { throw Error(`Not found '${exeFile.name}' !`) } + cmds = ['-S', objPath]; + } + break; + case 'AC5': + case 'AC6': + { + exeFile = File.fromArray([activePrj.getToolchain().getToolchainDir().path, 'bin', `fromelf.exe`]); + if (!exeFile.IsFile()) { throw Error(`Not found '${exeFile.name}' !`) } + cmds = ['-c', objPath]; + } + break; + default: + throw new Error(`Only support '${supportList.join(',')}' compiler !`); + } /* executable */ - const asmTxt = child_process.execFileSync(exeFile.path, ['-S', objPath], { encoding: 'ascii' }); + const asmTxt = child_process.execFileSync(exeFile.path, cmds, { encoding: 'ascii' }); const asmFile = `${srcPath}.edasm`; const asmFileUri = vscode.Uri.parse(VirtualDocument.instance().getUriByPath(asmFile)); VirtualDocument.instance().updateDocument(asmFile, asmTxt); @@ -2935,7 +3292,7 @@ export class ProjectExplorer { } private updateSettingsView(prj: AbstractProject) { - this.dataProvider.UpdateView(this.dataProvider.getCacheItem(prj, TreeItemType.SETTINGS)); + this.dataProvider.UpdateView(this.dataProvider.treeCache.getTreeItem(prj, TreeItemType.SETTINGS)); } async ModifyOtherSettings(item: ProjTreeItem) { @@ -2975,9 +3332,7 @@ export class ProjectExplorer { value: prjConfig.name, ignoreFocusOut: true, placeHolder: 'Input project name', - validateInput: (input: string): string | undefined => { - return input.trim() === '' ? 'project name can not be empty' : undefined; - } + validateInput: (name) => AbstractProject.validateProjectName(name) }); if (newName && newName !== prjConfig.name) { @@ -2998,6 +3353,165 @@ export class ProjectExplorer { } } + async ImportSourceFromExtProject(item: ProjTreeItem) { + + try { + // + // select importer + // + const scriptRoot = File.fromArray([ResManager.GetInstance().GetBinDir().path, 'scripts']); + const imptrFolder = File.fromArray([scriptRoot.path, 'importer']); + const items: any[] = []; + + imptrFolder.GetList([/^(?:[^\.]+)\.(?:[^\.]+)\.js$/i]) + .forEach((imptrFile) => { + const m = /^(?[^\.]+)\.(?[^\.]+)\.js$/i.exec(imptrFile.name); + if (m && m.groups) { + items.push({ + label: m.groups['type'].replace(/\-/g, ' ').replace(/_/g, ' '), + detail: `project file suffix: '${m.groups['suffix']}'`, + suffix: m.groups['suffix'], + file: imptrFile + }); + } + }); + + const imptrType: any = await vscode.window.showQuickPick(items, { + placeHolder: `Select an importer`, + canPickMany: false + }); + + if (imptrType == undefined) { + return; + } + + const filter: any = {}; + filter[imptrType.label] = [imptrType.suffix]; + + const uri = await vscode.window.showOpenDialog({ + openLabel: 'Import This File', + canSelectFiles: true, + filters: filter + }); + + if (uri == undefined) { + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: `Importing Resources` + }, (progress) => { + return new Promise(async (resolve) => { + try { + progress.report({ message: `running importer ...` }); + + // + // run importer + // + const prjFile = new File(uri[0].fsPath); + const imptrName = (imptrType.file).noSuffixName; + const cmds = ['--std', './importer/index.js', imptrName, prjFile.path]; + const result = child_process.execFileSync(`${scriptRoot.path}/qjs.exe`, cmds, { cwd: scriptRoot.path }).toString(); + + let prjList: ImporterProjectInfo[]; + try { + prjList = JSON.parse(result); + if (!Array.isArray(prjList)) throw new Error('project list must be an array !'); + } catch (error) { + throw new Error(`Import Error !, msg: '${result}'`); + } + + // + // select project + // + let prjInfo: ImporterProjectInfo | undefined; + + if (prjList.length > 0) { + // if have multi project, select one to import + if (prjList.length > 1) { + const item = await vscode.window.showQuickPick( + prjList.map((prj) => { + return { + label: prj.name, + description: prj.target, + detail: `${prjFile.name} -> ${prj.name}${prj.target ? (': ' + prj.target) : ''}` + } + }), + { + placeHolder: `Found ${prjList.length} sub project, select one to import`, + ignoreFocusOut: true, + canPickMany: false + } + ); + if (item != undefined) { + const index = prjList.findIndex((prj) => prj.name == item.label); + if (index != -1) { + prjInfo = prjList[index]; + } + } + } + // if only have one, use it + else { + prjInfo = prjList[0]; + } + } + + if (prjInfo == undefined) { + resolve(); + return; + } + + // make abs path to relative path + const formatVirtualFolder = (vFolderRoot: VirtualFolder) => { + const folderStack: VirtualFolder[] = [vFolderRoot]; + while (folderStack.length > 0) { + const vFolder = folderStack.pop(); + if (vFolder) { + vFolder.files = vFolder.files.map((file) => { + return { path: prj.ToRelativePath(file.path) || file.path } + }); + vFolder.folders.forEach((folder) => { + folderStack.push(folder) + }); + } + } + }; + + // + // start import project + // + const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); + const prjConf = prj.GetConfiguration(); + prjConf.config.virtualFolder = prjInfo.files; + formatVirtualFolder(prjConf.config.virtualFolder); + const deps = prjConf.CustomDep_getDependence(); + deps.incList = prjInfo.incList; + deps.libList = []; + deps.defineList = prjInfo.defineList; + + // + // notify update + // + prj.getVirtualSourceManager().load(); + prjConf.CustomDep_NotifyChanged(); + + // show message and exit + progress.report({ message: `done !` }); + setTimeout(() => resolve(), 1000); + + } catch (error) { + GlobalEvent.emit('error', error); + resolve(); + } + }); + }); + + } catch (error) { + GlobalEvent.emit('error', error); + } + } + async AddIncludeDir(prjIndex: number) { const prj = this.dataProvider.GetProjectByIndex(prjIndex); const uri = await vscode.window.showOpenDialog({ @@ -3178,9 +3692,12 @@ export class ProjectExplorer { const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); + // if it's a virtual file, we use virtual path if (item.type === TreeItemType.V_FILE_ITEM) { prj.excludeSourceFile((item.val.obj).path); } + + // if it's a fs file, we use fs path else if (item.val.value instanceof File) { prj.excludeSourceFile(item.val.value.path); } @@ -3236,6 +3753,151 @@ export class ProjectExplorer { } } + async showFileInExplorer(item: ProjTreeItem) { + + let file: File | undefined; + + if (item.val.value instanceof File) { // if value is a file, use it + file = new File(NodePath.normalize(item.val.value.path)); + } + + if (file) { + vscode.commands.executeCommand('revealFileInOS', vscode.Uri.file(file.path)); + } + } + + // KV: + private prjFolderSourceChangesMap: Map = new Map(); + async modifySourcesPath(item: ProjTreeItem) { + + const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); + const vSourceManager = prj.getVirtualSourceManager(); + + // virtual file + if (item.type === TreeItemType.V_FILE_ITEM || + item.type === TreeItemType.V_EXCFILE_ITEM) { + + const vInfo = item.val.obj; + const path = await vscode.window.showInputBox({ + value: vInfo.vFile.path, + ignoreFocusOut: true, + prompt: `Input a file path (allow relative path)` + }); + + if (path == undefined) { + return; + } + + const repath = prj.ToRelativePath(path) || path; + const vFileInfo = vSourceManager.getFile(vInfo.path); + if (vFileInfo) { + vFileInfo.path = repath; + vSourceManager.notifyUpdateFile(vInfo.path); + } else { + GlobalEvent.emit('msg', newMessage('Error', `Internal error: can't get obj from virtual path: '${vInfo.path}'`)); + } + } + + // virtual folder + else if (item.type === TreeItemType.V_FOLDER || + item.type === TreeItemType.V_FOLDER_ROOT) { + + const vInfo = item.val.obj; + const vFolderInfo = vSourceManager.getFolder(vInfo.path); + if (vFolderInfo) { + + const getOldFileNameByProject = (vPath: string, prj: AbstractProject) => { + for (const KV of this.prjFolderSourceChangesMap) { + if (KV[1].vFolderPath == vPath && + KV[1].project.getWsPath().toLowerCase() == prj.getWsPath().toLowerCase()) { + return KV[0]; + } + } + }; + + let tmpFile: File; + + let oldName = getOldFileNameByProject(vInfo.path, prj); + if (oldName) { + tmpFile = File.fromArray([os.tmpdir(), oldName]); + } else { // if file not exist, add to mapper + tmpFile = File.fromArray([os.tmpdir(), `eide-vfolder-${Date.now()}.yaml`]); + this.prjFolderSourceChangesMap.set(tmpFile.name, { vFolderPath: vInfo.path, project: prj }); + } + + const yamlLines: string[] = [ + `#`, + `# You can modify files path by editing and saving this file (allow relative path).`, + `#`, + ``, + yml.stringify(vFolderInfo.files, { indent: 4 }) + ]; + + tmpFile.Write(yamlLines.join(os.EOL)); + vscode.window.showTextDocument(vscode.Uri.file(tmpFile.path), { preview: false }); + + } else { + GlobalEvent.emit('msg', newMessage('Error', `Internal error: can't get obj from virtual path: '${vInfo.path}'`)); + } + } + } + + // callbk, if config file saved, apply the changes + private onFolderSourceYamlSaved(doc: vscode.TextDocument) { + + const tmpFileName = NodePath.basename(doc.fileName); + const info = this.prjFolderSourceChangesMap.get(tmpFileName); + + // skip irrelevant files + if (info == undefined) return; + + // save to config + try { + + const vSrcManger = info.project.getVirtualSourceManager(); + const vFolderInfo = vSrcManger.getFolder(info.vFolderPath); + if (!vFolderInfo) { + throw new Error(`Virtual folder '${info.vFolderPath}' is not exist !`); + } + + let fileList: VirtualFile[] = yml.parse(doc.getText()); + if (fileList != undefined && !Array.isArray(fileList)) { + throw new Error(`Type error, files list must be an array, please check your yaml config file !`); + } + + // convert to repath + if (fileList) { + fileList = fileList.map((vFile) => { + return { + path: info.project.ToRelativePath(vFile.path) || vFile.path + }; + }); + } + + vFolderInfo.files = fileList || []; + vSrcManger.notifyUpdateFolder(info.vFolderPath); + + } catch (error) { + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Warning')); + } + } + + // callbk, if config file closed, rm tmp file + private onFolderSourceYamlClosed(doc: vscode.TextDocument) { + + // skip irrelevant files + const tmpFileName = NodePath.basename(doc.fileName); + if (!this.prjFolderSourceChangesMap.has(tmpFileName)) return; + + // do + try { + this.prjFolderSourceChangesMap.delete(tmpFileName); // remove from mapper + fs.unlinkSync(`${os.tmpdir()}/${tmpFileName}`); + } catch (error) { + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Hidden')); + } + } + CopyItemValue(item: ProjTreeItem) { if (item.val.value instanceof File) { vscode.env.clipboard.writeText(item.val.value.path); @@ -3244,6 +3906,153 @@ export class ProjectExplorer { } } + async showFilesOptions(item: ProjTreeItem) { + const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); + const optFile = prj.getFilesOptionsFile(); + vscode.window.showTextDocument(vscode.Uri.parse(optFile.ToUri()), { preview: true }); + } + + // callbk, if config file saved, apply the changes + private onCustomDepYamlSaved(doc: vscode.TextDocument) { + + const tmpFileName = NodePath.basename(doc.fileName); + const prj = this.prjCusDepChangesMap.get(tmpFileName); + + // skip irrelevant files + if (prj == undefined) return; + + // save to config + try { + + const cusDep = prj.GetConfiguration().CustomDep_getDependence(); + const cfg = yml.parse(doc.getText()); + + // inc list + if (Array.isArray(cfg.IncludeFolders)) { + const li = cfg.IncludeFolders + .filter((path: any) => typeof (path) == 'string') + .map((path: string) => prj.ToAbsolutePath(path)); + cusDep.incList = ArrayDelRepetition(li); + } else { + cusDep.incList = []; + } + + // lib list + if (Array.isArray(cfg.LibraryFolders)) { + const li = cfg.LibraryFolders + .filter((path: any) => typeof (path) == 'string') + .map((path: string) => prj.ToAbsolutePath(path)); + cusDep.libList = ArrayDelRepetition(li); + } else { + cusDep.libList = []; + } + + // macro list + if (Array.isArray(cfg.Defines)) { + const li = cfg.Defines + .filter((path: any) => typeof (path) == 'string'); + cusDep.defineList = ArrayDelRepetition(li); + } else { + cusDep.defineList = []; + } + + prj.GetConfiguration().CustomDep_NotifyChanged(); + + } catch (error) { + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Warning')); + } + } + + // callbk, if config file closed, rm tmp file + private onCustomDepYamlClosed(doc: vscode.TextDocument) { + + // skip irrelevant files + const tmpFileName = NodePath.basename(doc.fileName); + if (!this.prjCusDepChangesMap.has(tmpFileName)) return; + + // do + try { + this.prjCusDepChangesMap.delete(tmpFileName); // remove from mapper + fs.unlinkSync(`${os.tmpdir()}/${tmpFileName}`); + } catch (error) { + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Hidden')); + } + } + + // KV: + private prjCusDepChangesMap: Map = new Map(); + async ModifyCustomDependence(item: ProjTreeItem) { + + const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); + const cusDep = prj.GetConfiguration().CustomDep_getDependence(); + + // gen deps yaml content + const yamlLines: string[] = [ + `#`, + `# You can modify the configuration by editing and saving this file.`, + `#` + ]; + + // fill data + { + // push include path + yamlLines.push( + ``, + `# Header Include Path`, + `IncludeFolders:`, + `# - ./Your/Include/Folder/Path` + ); + cusDep.incList.forEach((path) => { + yamlLines.push(` - ${prj.ToRelativePath(path) || path}`) + }); + + // push lib folder path + yamlLines.push( + ``, + `# Library Search Path`, + `LibraryFolders:`, + `# - ./Your/Library/Path` + ); + cusDep.libList.forEach((path) => { + yamlLines.push(` - ${prj.ToRelativePath(path) || path}`) + }); + + // push macros + yamlLines.push( + ``, + `# Preprocessor Definitions`, + `Defines:`, + `# - TEST=1` + ); + cusDep.defineList.forEach((macro) => { + yamlLines.push(` - ${macro}`) + }); + } + + const getTmpPathByProject = (prj: AbstractProject) => { + for (const KV of this.prjCusDepChangesMap) { + if (KV[1].getWsPath().toLowerCase() == prj.getWsPath().toLowerCase()) { + return KV[0]; + } + } + }; + + // write and open file + const yamlStr = yamlLines.join(os.EOL); + const oldName = getTmpPathByProject(prj); + let tmpFile: File; + + if (oldName) { + tmpFile = File.fromArray([os.tmpdir(), oldName]); + } else {// if file not exist, add to mapper + tmpFile = File.fromArray([os.tmpdir(), `eide-deps-${Date.now()}.yaml`]); + this.prjCusDepChangesMap.set(tmpFile.name, prj); + } + + tmpFile.Write(yamlStr); + vscode.window.showTextDocument(vscode.Uri.parse(tmpFile.ToUri()), { preview: false }); + } + RemoveDependenceItem(item: ProjTreeItem) { const prj = this.dataProvider.GetProjectByIndex(item.val.projectIndex); switch ((item.val.obj).type) { @@ -3366,12 +4175,75 @@ export class ProjectExplorer { }; try { + + // try to show it by eide, if failed, show it + // by vscode default api + if (this.showBinaryFiles(file, isPreview)) return; + /* We need use 'vscode.open' command, not 'showTextDocument' API, * because API can't open bin file */ vscode.commands.executeCommand('vscode.open', vsUri, { preview: isPreview }); + } catch (error) { GlobalEvent.emit('msg', ExceptionToMessage(error, 'Warning')); } } } + + private showBinaryFiles(binFile: File, isPreview?: boolean): boolean | undefined { + + try { + + const suffix = binFile.suffix.toLowerCase(); + + // show armcc axf file + if (suffix == '.axf') { + + const fromelf = File.fromArray([ + SettingManager.GetInstance().getArmcc5Dir().path, 'bin', 'fromelf.exe' + ]); + + if (!fromelf.IsFile()) return; + + const cont = child_process + .execFileSync(fromelf.path, ['--text', '-e', binFile.path]) + .toString(); + + const vDoc = VirtualDocument.instance(); + const docName = `${binFile.path}.info`; + vDoc.updateDocument(docName, cont); + + const uri = vscode.Uri.parse(vDoc.getUriByPath(docName)); + vscode.window.showTextDocument(uri, { preview: isPreview }); + + return true; + } + + // show gnu elf file + else if (suffix == '.elf') { + + const readelf = File.fromArray([ + ResManager.GetInstance().getBuilderDir(), 'readelf.exe' + ]); + + if (!readelf.IsFile()) return; + + const cont = child_process + .execFileSync(readelf.path, ['-e', binFile.path]) + .toString(); + + const vDoc = VirtualDocument.instance(); + const docName = `${binFile.path}.info`; + vDoc.updateDocument(docName, cont); + + const uri = vscode.Uri.parse(vDoc.getUriByPath(docName)); + vscode.window.showTextDocument(uri, { preview: isPreview }); + + return true; + } + + } catch (error) { + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Hidden')); + } + } } diff --git a/src/EIDETypeDefine.ts b/src/EIDETypeDefine.ts index 407276e4..127b311c 100644 --- a/src/EIDETypeDefine.ts +++ b/src/EIDETypeDefine.ts @@ -60,12 +60,12 @@ import * as os from 'os'; import * as vscode from 'vscode'; import * as NodePath from 'path'; import { isNullOrUndefined } from "util"; -import { AbstractProject } from "./EIDEProject"; +import { AbstractProject, VirtualSource } from "./EIDEProject"; import { SettingManager } from "./SettingManager"; import { jsonc } from 'jsonc'; import { WorkspaceManager } from "./WorkspaceManager"; -import * as utility from './utility' +import * as utility from './utility'; // ------------------------------------------- @@ -83,7 +83,7 @@ export interface FileItem { } export interface FileGroup { - name: string; + name: string; // dir name if it's system folder, else it's a virtual path files: FileItem[]; disabled?: boolean; // for mdk group info } @@ -432,6 +432,8 @@ export interface ProjectTargetInfo { } export interface VirtualFile { + // this must be an relative path + // because virtual file path may be outside the project root directory path: string; } @@ -442,6 +444,7 @@ export interface VirtualFolder { } export interface ProjectConfigData { + name: string; type: ProjectType; mode: string; // target name @@ -459,7 +462,7 @@ export interface ProjectConfigData { // source srcDirs: string[]; - virtualFolder: VirtualFolder[]; + virtualFolder: VirtualFolder; dependenceList: DependenceGroup[]; outDir: string; @@ -492,6 +495,15 @@ export class ProjectConfiguration constructor(f: File, type?: ProjectType) { super(f, type); + // compate old version + if (Array.isArray(this.config.virtualFolder)) { + this.config.virtualFolder = { + name: VirtualSource.rootName, + files: [], + folders: this.config.virtualFolder + }; + } + this.compileConfigModel = CompileConfigModel.getInstance(this.config); this.uploadConfigModel = UploadConfigModel.getInstance(this.config.uploader); @@ -522,7 +534,7 @@ export class ProjectConfiguration private toAbsolutePath(path: string): string { const _path = path.trim(); - if (NodePath.isAbsolute(_path)) { + if (File.isAbsolute(_path)) { return _path; } return NodePath.normalize(this.getRootDir().path + File.sep + _path); @@ -530,12 +542,16 @@ export class ProjectConfiguration private toRelativePath(path: string): string { - if (!NodePath.isAbsolute(path)) { + if (File.isEnvPath(path)) { // env path have no repath + return path; + } + + if (!File.isAbsolute(path)) { return path; } const rePath = NodePath.relative(this.getRootDir().path, path); - if (NodePath.isAbsolute(rePath)) { + if (File.isAbsolute(rePath)) { return rePath; } @@ -591,7 +607,7 @@ export class ProjectConfiguration dependenceList: [], compileConfig: SdccCompileConfigModel.getDefaultConfig(), srcDirs: [], - virtualFolder: [], + virtualFolder: { name: VirtualSource.rootName, files: [], folders: [] }, excludeList: [], outDir: '.\\build', deviceName: null, @@ -612,7 +628,7 @@ export class ProjectConfiguration compileConfig: GccCompileConfigModel.getDefaultConfig(), uploader: 'JLink', srcDirs: [], - virtualFolder: [], + virtualFolder: { name: VirtualSource.rootName, files: [], folders: [] }, excludeList: [], outDir: '.\\build', deviceName: null, @@ -633,7 +649,7 @@ export class ProjectConfiguration compileConfig: RiscvCompileConfigModel.getDefaultConfig(), uploader: 'JLink', srcDirs: [], - virtualFolder: [], + virtualFolder: { name: VirtualSource.rootName, files: [], folders: [] }, excludeList: [], outDir: '.\\build', deviceName: null, @@ -1026,6 +1042,10 @@ export class ProjectConfiguration return this.config.dependenceList[index].depList[depIndex]; } + CustomDep_NotifyChanged() { + this.emit('dataChanged', { type: 'dependence' }); + } + CustomDep_RemoveInvalidIncDirs() { const dep = this.CustomDep_getDependence(); dep.incList = dep.incList.filter((_path) => { return new File(_path).IsDir(); }); @@ -2258,6 +2278,55 @@ export abstract class UploadConfigModel extends ConfigModel { return 'none'; } } + + getKeyIcon(key: string): KeyIcon | undefined { + switch (key) { + case 'bin': + return 'BinaryFile_16x.svg'; + default: + return 'Property_16x.svg'; + } + } + + getKeyValue(key: string): string { + switch (key) { + case 'bin': + return (this.data)[key] || '${projectName}.hex'; + default: + return (this.data)[key] || 'null'; + } + } + + protected GetKeyType(key: string): FieldType { + switch (key) { + case 'bin': + return 'INPUT'; + default: + return 'Disable'; + } + } + + protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { + switch (key) { + default: + return undefined; + } + } + + protected getEventData(key: string): EventData | undefined { + switch (key) { + default: + return undefined; + } + } + + protected VerifyString(key: string, input: string): string | undefined { + return undefined; + } + + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { + return undefined; + } } class StcgalUploadModel extends UploadConfigModel { @@ -2277,13 +2346,12 @@ class StcgalUploadModel extends UploadConfigModel { getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': case 'eepromImgPath': return 'BinaryFile_16x.svg'; case 'options': return 'ConfigurationEditor_16x.svg'; default: - return undefined; + return super.getKeyIcon(key); } } @@ -2292,7 +2360,7 @@ class StcgalUploadModel extends UploadConfigModel { case 'options': return 'Object {...}'; default: - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } } @@ -2310,45 +2378,39 @@ class StcgalUploadModel extends UploadConfigModel { } }; default: - return undefined; + return super.getEventData(key); } } protected GetKeyType(key: string): FieldType { switch (key) { - case 'bin': - return 'OPEN_FILE'; case 'eepromImgPath': return 'INPUT'; case 'options': return 'EVENT'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 'bin', 's19'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } protected VerifyString(key: string, input: string): string | undefined { - return undefined; + return super.VerifyString(key, input); } - protected GetSelectionList(key: string): CompileConfigPickItem[] { - return []; + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { + return super.GetSelectionList(key); } GetDefault() { return { - bin: 'null', + bin: '', eepromImgPath: 'null', options: `${AbstractProject.EIDE_DIR}/stc.flash.json` }; @@ -2389,12 +2451,10 @@ class JLinkUploadModel extends UploadConfigModel { return 'CPU_16x.svg'; case 'proType': return 'ConnectUnplugged_16x.svg'; - case 'bin': - return 'BinaryFile_16x.svg'; case 'otherCmds': return 'terminal_16x.svg'; default: - return 'Property_16x.svg'; + return super.getKeyIcon(key); } } @@ -2422,12 +2482,12 @@ class JLinkUploadModel extends UploadConfigModel { case 'cpuInfo': return this.data.cpuInfo.cpuName; default: - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } } protected getEventData(key: string): EventData | undefined { - return undefined; + return super.getEventData(key); } protected GetKeyType(key: string): FieldType { @@ -2440,21 +2500,15 @@ class JLinkUploadModel extends UploadConfigModel { return 'INPUT'; case 'speed': return 'INPUT_INTEGER'; - case 'bin': - return 'OPEN_FILE'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 'bin', 's19'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } @@ -2465,11 +2519,11 @@ class JLinkUploadModel extends UploadConfigModel { case 'baseAddr': return /^0x[0-9a-f]{1,8}$/i.test(input) ? undefined : 'must be a hex number, like: 0x08000000'; default: - return undefined; + return super.VerifyString(key, input); } } - protected GetSelectionList(key: string): CompileConfigPickItem[] { + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { switch (key) { case 'proType': return this.protocolList.map((protocol) => { @@ -2487,13 +2541,13 @@ class JLinkUploadModel extends UploadConfigModel { }; }); default: - return []; + return super.GetSelectionList(key); } } GetDefault(): JLinkOptions { return { - bin: 'null', + bin: '', baseAddr: '0x08000000', cpuInfo: { vendor: 'ST', @@ -2562,8 +2616,6 @@ class STLinkUploadModel extends UploadConfigModel { getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': - return 'BinaryFile_16x.svg'; case 'proType': return 'ConnectUnplugged_16x.svg'; case 'optionBytes': @@ -2571,7 +2623,7 @@ class STLinkUploadModel extends UploadConfigModel { case 'otherCmds': return 'terminal_16x.svg'; default: - return 'Property_16x.svg'; + return super.getKeyIcon(key); } } @@ -2606,14 +2658,12 @@ class STLinkUploadModel extends UploadConfigModel { case 'elFile': return NodePath.basename(this.data.elFile); default: - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } } protected GetKeyType(key: string): FieldType { switch (key) { - case 'bin': - return 'OPEN_FILE'; case 'runAfterProgram': case 'proType': case 'resetMode': @@ -2627,18 +2677,14 @@ class STLinkUploadModel extends UploadConfigModel { case 'optionBytes': return 'EVENT'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 'bin', 's19'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } @@ -2649,11 +2695,11 @@ class STLinkUploadModel extends UploadConfigModel { case 'address': return /^0x[0-9a-f]+$/i.test(input) ? undefined : 'must be a hex number'; default: - return undefined; + return super.VerifyString(key, input); } } - protected GetSelectionList(key: string): CompileConfigPickItem[] { + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { switch (key) { case 'runAfterProgram': return [ @@ -2699,7 +2745,7 @@ class STLinkUploadModel extends UploadConfigModel { return resultList; } default: - return []; + return super.GetSelectionList(key); } } @@ -2720,7 +2766,7 @@ class STLinkUploadModel extends UploadConfigModel { }; } default: - return undefined; + return super.getEventData(key); } } @@ -2757,45 +2803,42 @@ class StvpUploadModel extends UploadConfigModel { } getKeyValue(key: string): string { - return (this.data)[key]; + return super.getKeyValue(key); } protected GetKeyType(key: string): FieldType { switch (key) { case 'deviceName': return 'SELECTION'; - case 'bin': case 'eepromFile': case 'optionByteFile': return 'INPUT'; default: - return 'Disable'; + return super.GetKeyType(key); } } getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': case 'eepromFile': case 'optionByteFile': return 'BinaryFile_16x.svg'; case 'deviceName': return 'CPU_16x.svg'; default: - return 'Property_16x.svg'; + return super.getKeyIcon(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { default: - return undefined; + return super.GetOpenFileFilters(key); } } protected VerifyString(key: string, input: string): string | undefined { switch (key) { - case 'bin': case 'eepromFile': case 'optionByteFile': if (/(?:\.hex|\.s19)$/i.test(input) || /^null$/.test(input)) { @@ -2804,11 +2847,11 @@ class StvpUploadModel extends UploadConfigModel { return 'the value must be a hex/s19 file path or \'null\''; } default: - return undefined; + return super.VerifyString(key, input); } } - protected GetSelectionList(key: string): CompileConfigPickItem[] { + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { switch (key) { case 'deviceName': return ResManager.GetInstance().getStm8DevList() @@ -2818,18 +2861,18 @@ class StvpUploadModel extends UploadConfigModel { }; }); default: - return []; + return super.GetSelectionList(key); } } protected getEventData(key: string): EventData | undefined { - return undefined; + return super.getEventData(key); } GetDefault(): STVPFlasherOptions { return { deviceName: 'STM8S105x4', - bin: 'null', + bin: '', eepromFile: 'null', optionByteFile: 'null' }; @@ -2869,7 +2912,7 @@ class PyOCDUploadModel extends UploadConfigModel { case 'config': return 'Object {...}'; default: - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } } @@ -2884,8 +2927,6 @@ class PyOCDUploadModel extends UploadConfigModel { getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': - return 'BinaryFile_16x.svg'; case 'targetName': return 'CPU_16x.svg'; case 'speed': @@ -2895,14 +2936,12 @@ class PyOCDUploadModel extends UploadConfigModel { case 'config': return 'ConfigurationEditor_16x.svg'; default: - return undefined; + return super.getKeyIcon(key); } } protected GetKeyType(key: string): FieldType { switch (key) { - case 'bin': - return 'OPEN_FILE'; case 'targetName': return 'INPUT'; case 'speed': @@ -2912,18 +2951,14 @@ class PyOCDUploadModel extends UploadConfigModel { case 'config': return 'EVENT'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 'bin', 's19'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } @@ -2936,14 +2971,14 @@ class PyOCDUploadModel extends UploadConfigModel { case 'targetName': return /^[^\s]+$/i.test(input) ? undefined : 'must be a chip name, like: stm32f103c8'; default: - return undefined; + return super.VerifyString(key, input); } } - protected GetSelectionList(key: string): CompileConfigPickItem[] { + protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { switch (key) { default: - return []; + return super.GetSelectionList(key); } } @@ -2958,13 +2993,13 @@ class PyOCDUploadModel extends UploadConfigModel { } }; default: - return undefined; + return super.getEventData(key); } } GetDefault(): PyOCDFlashOptions { return { - bin: 'null', + bin: '', targetName: 'cortex_m', baseAddr: '0x08000000', speed: '4M', @@ -3007,7 +3042,7 @@ class OpenOCDUploadModel extends UploadConfigModel { } return 'null'; default: - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } } @@ -3022,8 +3057,6 @@ class OpenOCDUploadModel extends UploadConfigModel { getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': - return 'BinaryFile_16x.svg'; case 'target': return 'CPU_16x.svg'; case 'interface': @@ -3031,14 +3064,12 @@ class OpenOCDUploadModel extends UploadConfigModel { case 'baseAddr': return 'Property_16x.svg'; default: - return undefined; + return super.getKeyIcon(key); } } protected GetKeyType(key: string): FieldType { switch (key) { - case 'bin': - return 'OPEN_FILE'; case 'target': return 'SELECTION'; case 'interface': @@ -3046,18 +3077,14 @@ class OpenOCDUploadModel extends UploadConfigModel { case 'baseAddr': return 'INPUT'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 's19', 'bin'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } @@ -3066,7 +3093,7 @@ class OpenOCDUploadModel extends UploadConfigModel { case 'baseAddr': return /^0x[0-9a-f]{1,8}$/i.test(input) ? undefined : 'must be a hex number, like: 0x08000000'; default: - return undefined; + return super.VerifyString(key, input); } } @@ -3121,17 +3148,17 @@ class OpenOCDUploadModel extends UploadConfigModel { }; }); default: - return undefined; + return super.GetSelectionList(key); } } protected getEventData(key: string): EventData | undefined { - return undefined; + return super.getEventData(key); } GetDefault(): OpenOCDFlashOptions { return { - bin: 'null', + bin: '', target: 'stm32f1x', interface: 'stlink', baseAddr: '0x08000000' @@ -3153,7 +3180,7 @@ class CustomUploadModel extends UploadConfigModel { } getKeyValue(key: string): string { - return (this.data)[key] || 'null'; + return super.getKeyValue(key); } isKeyEnable(key: string): boolean { @@ -3165,58 +3192,50 @@ class CustomUploadModel extends UploadConfigModel { getKeyIcon(key: string): KeyIcon | undefined { switch (key) { - case 'bin': - return 'BinaryFile_16x.svg'; case 'commandLine': return 'terminal_16x.svg'; default: - return undefined; + return super.getKeyIcon(key); } } protected GetKeyType(key: string): FieldType { switch (key) { - case 'bin': - return 'OPEN_FILE'; case 'commandLine': return 'INPUT'; default: - return 'Disable'; + return super.GetKeyType(key); } } protected GetOpenFileFilters(key: string): OpenFileFilter | undefined { switch (key) { - case 'bin': - return { - 'program file': ['hex', 's19', 'bin'] - }; default: - return undefined; + return super.GetOpenFileFilters(key); } } protected VerifyString(key: string, input: string): string | undefined { switch (key) { default: - return undefined; + return super.VerifyString(key, input); } } protected GetSelectionList(key: string): CompileConfigPickItem[] | undefined { switch (key) { default: - return undefined; + return super.GetSelectionList(key); } } protected getEventData(key: string): EventData | undefined { - return undefined; + return super.getEventData(key); } GetDefault(): CustomFlashOptions { return { - bin: 'null', + bin: '', commandLine: 'null' }; } diff --git a/src/HexUploader.ts b/src/HexUploader.ts index f8cf96ff..020f1c21 100644 --- a/src/HexUploader.ts +++ b/src/HexUploader.ts @@ -47,6 +47,8 @@ let _mInstance: HexUploaderManager | undefined; export type HexUploaderType = 'JLink' | 'STVP' | 'STLink' | 'stcgal' | 'pyOCD' | 'OpenOCD' | 'Custom'; export interface UploadOption { + // program file path + // format: "[,addr1];[,addr2]" bin: string; } @@ -123,10 +125,19 @@ export class HexUploaderManager { //------------------------------------------------------ interface UploaderPreData { + isOk: boolean | Error; + params?: T; } +interface FlashProgramFile { + + path: string; + + addr?: string; +}; + export abstract class HexUploader { abstract readonly toolType: HexUploaderType; @@ -141,9 +152,9 @@ export abstract class HexUploader { this.isPowershell = /powershell.exe$/i.test(vscode.env.shell); } - async upload() { + async upload(eraseAll?: boolean) { - const dat = await this._prepare(); + const dat = await this._prepare(eraseAll); if (dat.isOk === false) { // canceled return; @@ -158,19 +169,46 @@ export abstract class HexUploader { } protected getUploadOptions(): T { - const option: T = JSON.parse(JSON.stringify(this.project.GetConfiguration().uploadConfigModel.data)); - option.bin = this.project.ToAbsolutePath(option.bin); - return option; + return JSON.parse(JSON.stringify(this.project.GetConfiguration().uploadConfigModel.data)); + } + + protected parseProgramFiles(options: T): FlashProgramFile[] { + + const result: FlashProgramFile[] = []; + const matcher = /(?[^,]+)(?:,(?0x[a-f0-9]+))?/i; + + // if 'bin' path is empty, use default program path + if (options.bin.trim() === '') { + + const hexPath = [ + this.project.getOutputDir(), + this.project.GetConfiguration().config.name + '.hex' + ].join(File.sep); + + return [{ path: this.project.ToAbsolutePath(hexPath) }]; + } + + options.bin.split(';').forEach((path) => { + const m = matcher.exec(path); + if (m && m.groups && m.groups['path']) { + result.push({ + path: this.project.ToAbsolutePath(m.groups['path']), + addr: m.groups['addr'] + }); + } + }); + + return result; } protected toAbsolute(_path: string): File { const nomalPath = NodePath.normalize(_path); - const file = NodePath.isAbsolute(nomalPath) ? + const file = File.isAbsolute(nomalPath) ? new File(nomalPath) : File.fromArray([this.project.GetRootDir().path, nomalPath]); return file; } - protected abstract _prepare(): Promise>; + protected abstract _prepare(eraseAll?: boolean): Promise>; protected abstract _launch(params: InvokeParamsType | undefined): void; } @@ -209,33 +247,61 @@ class JLinkUploader extends HexUploader { toolType: HexUploaderType = 'JLink'; - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { if (!new File(SettingManager.GetInstance().getJlinkDir()).IsDir()) { await ResInstaller.instance().setOrInstallTools(this.toolType, `Not found 'JLink' install directory !`); return { isOk: false }; } - const option = this.getUploadOptions(); + const jlinkCmdFileLines: string[] = []; // create output dir const outFolder = new File(this.project.ToAbsolutePath(this.project.getOutputDir())); outFolder.CreateDir(true); + const jlinkCommandsFile = File.fromArray([outFolder.path, 'commands.jlink']); - // clear base addr for hex file - if (/\.hex$/i.test(option.bin)) { - option.baseAddr = undefined; + const option = this.getUploadOptions(); + const files = this.parseProgramFiles(option); + + // program + if (!eraseAll) { + + if (files.length == 0) { + return { isOk: new Error(`no any program files !`) }; + } + + jlinkCmdFileLines.push( + 'r', + 'halt' + ); + + files.forEach((file) => { + if (/\.bin$/i.test(file.path)) { + const addr = file.addr || option.baseAddr + jlinkCmdFileLines.push(`loadfile "${file.path}"${addr ? (`,${addr}`) : ''}`); + } else { + jlinkCmdFileLines.push(`loadfile "${file.path}"`); + } + }); + + jlinkCmdFileLines.push( + 'r', + 'go', + 'exit' + ); } - const jlinkCommandsFile = File.fromArray([outFolder.path, 'commands.jlink']); - const jlinkCmdFileLines: string[] = [ - 'r', - 'halt', - `loadfile "${option.bin}"${option.baseAddr ? (`,${option.baseAddr}`) : ''}`, - 'r', - 'go', - 'exit' - ]; + // erase internal falsh + else { + jlinkCmdFileLines.push( + 'r', + 'halt', + 'erase', + 'r', + 'exit' + ); + } const codeConverter = new CodeConverter(); const codePage = ResManager.getLocalCodePage(); @@ -301,7 +367,7 @@ class StcgalUploader extends HexUploader { super(prj); } - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const resManager = ResManager.GetInstance(); @@ -385,8 +451,13 @@ class StcgalUploader extends HexUploader { protected _launch(commands: string[]): void { const option = this.getUploadOptions(); + const programs = this.parseProgramFiles(option); - commands.push('"' + option.bin + '"'); + if (programs.length == 0) { + throw new Error(`no any program files !`); + } + + commands.push('"' + programs[0].path + '"'); const eepromFile = this.toAbsolute(option.eepromImgPath); if (eepromFile.IsFile()) { @@ -430,10 +501,15 @@ class STLinkUploader extends HexUploader { super(prj); } - private genCommandForStlinkCli(exe: File): string[] { + private genCommandForStlinkCli(exe: File, eraseAll?: boolean): string[] { const commands: string[] = []; const options = this.getUploadOptions(); + const programs = this.parseProgramFiles(options); + + if (programs.length == 0) { + throw new Error(`no any program files !`); + } /* connection commands */ commands.push( @@ -447,35 +523,45 @@ class STLinkUploader extends HexUploader { } /* flash commands */ - commands.push( - '-P', options.bin - ); + if (!eraseAll) { - if (/\.bin$/i.test(options.bin)) { - commands.push(options.address || '0x08000000'); - } + commands.push( + '-P', programs[0].path + ); - if (/\.stldr$/i.test(options.elFile)) { - const elFolder = File.fromArray([NodePath.dirname(exe.path), 'ExternalLoader']); - commands.push('-EL', options.elFile.replace('', elFolder.path)); - } + if (/\.bin$/i.test(programs[0].path)) { + commands.push(options.address || programs[0].addr || '0x08000000'); + } - // option bytes commands - const optionFile = new File(this.project.ToAbsolutePath(options.optionBytes)); - if (optionFile.IsFile()) { - const conf = ini.parse(optionFile.Read()); - const confList: string[] = []; - for (const key in conf) { confList.push(`${key}=${conf[key]}`) } - if (confList.length > 0) { - commands.push('-OB'); - confList.forEach((val) => { commands.push(val) }); - commands.push('-rOB'); + if (/\.stldr$/i.test(options.elFile)) { + const elFolder = File.fromArray([NodePath.dirname(exe.path), 'ExternalLoader']); + commands.push('-EL', options.elFile.replace('', elFolder.path)); } + + // option bytes commands + const optionFile = new File(this.project.ToAbsolutePath(options.optionBytes)); + if (optionFile.IsFile()) { + const conf = ini.parse(optionFile.Read()); + const confList: string[] = []; + for (const key in conf) { confList.push(`${key}=${conf[key]}`) } + if (confList.length > 0) { + commands.push('-OB'); + confList.forEach((val) => { commands.push(val) }); + commands.push('-rOB'); + } + } + + commands.push( + '-V', 'after_programming' + ); } - commands.push( - '-V', 'after_programming' - ); + // eraseAll + else { + commands.push( + '-ME' + ); + } /* misc commands */ commands.push( @@ -490,10 +576,15 @@ class STLinkUploader extends HexUploader { return commands; } - private genCommandForCubeProgramer(exe: File): string[] { + private genCommandForCubeProgramer(exe: File, eraseAll?: boolean): string[] { const commands: string[] = []; const options = this.getUploadOptions(); + const programs = this.parseProgramFiles(options); + + if (programs.length == 0) { + throw new Error(`no any program files !`); + } /* connect cmd */ commands.push('-c', `port=${options.proType}`, `freq=${options.speed}`); @@ -503,41 +594,52 @@ class STLinkUploader extends HexUploader { commands.push(`reset=${options.resetMode}`); } - /* external loader */ - if (/\.stldr$/i.test(options.elFile)) { - const elFolder = File.fromArray([NodePath.dirname(exe.path), 'ExternalLoader']); - commands.push('-el', options.elFile.replace('', elFolder.path)); - } + // program + if (!eraseAll) { - // option bytes commands - const optionFile = new File(this.project.ToAbsolutePath(options.optionBytes)); - if (optionFile.IsFile()) { - const conf = ini.parse(optionFile.Read()); - const confList: string[] = []; - for (const key in conf) { confList.push(`${key}=${conf[key]}`) } - if (confList.length > 0) { - commands.push('-ob'); - confList.forEach((val) => { commands.push(val) }); - commands.push('-ob', 'displ'); + /* external loader */ + if (/\.stldr$/i.test(options.elFile)) { + const elFolder = File.fromArray([NodePath.dirname(exe.path), 'ExternalLoader']); + commands.push('-el', options.elFile.replace('', elFolder.path)); + } + + // option bytes commands + const optionFile = new File(this.project.ToAbsolutePath(options.optionBytes)); + if (optionFile.IsFile()) { + const conf = ini.parse(optionFile.Read()); + const confList: string[] = []; + for (const key in conf) { confList.push(`${key}=${conf[key]}`) } + if (confList.length > 0) { + commands.push('-ob'); + confList.forEach((val) => { commands.push(val) }); + commands.push('-ob', 'displ'); + } } - } - /* download program */ - commands.push('--download', options.bin); + /* download program */ + commands.push('--download', programs[0].path); - if (/\.bin$/i.test(options.bin)) { - commands.push(options.address || '0x08000000'); + if (/\.bin$/i.test(programs[0].path)) { + commands.push(options.address || programs[0].addr || '0x08000000'); + } + + /* verify program */ + commands.push('-v'); } - /* verify program */ - commands.push('-v'); + // erase all + else { + commands.push( + '-e', 'all' + ); + } if (options.runAfterProgram) { commands.push('--go') } return commands; } - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const exe = new File(SettingManager.GetInstance().getSTLinkExePath()); if (!exe.IsFile()) { @@ -549,12 +651,12 @@ class STLinkUploader extends HexUploader { /* use stlink cli */ if (exe.noSuffixName.toLowerCase().startsWith('st-link_cli')) { - commands = this.genCommandForStlinkCli(exe); + commands = this.genCommandForStlinkCli(exe, eraseAll); } /* use cube programer */ else { - commands = this.genCommandForCubeProgramer(exe); + commands = this.genCommandForCubeProgramer(exe, eraseAll); } return { @@ -596,7 +698,7 @@ class STVPHexUploader extends HexUploader { super(prj); } - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const exe = new File(SettingManager.GetInstance().getStvpExePath()); if (!exe.IsFile()) { @@ -605,6 +707,8 @@ class STVPHexUploader extends HexUploader { } const options = this.getUploadOptions(); + const programs = this.parseProgramFiles(options); + const commands: string[] = []; // connection commands @@ -618,28 +722,41 @@ class STVPHexUploader extends HexUploader { commands.push('-no_loop'); commands.push('-no_log'); - // not verify - commands.push('-no_verif'); + // program + if (!eraseAll) { - const binFile = this.toAbsolute(options.bin); - if (binFile.IsFile()) { - commands.push('-FileProg=\"' + binFile.path + '\"'); - } else { - commands.push('-no_progProg'); - } + if (programs.length == 0) { + throw new Error(`no any program files !`); + } - const eepromFile = this.toAbsolute(options.eepromFile); - if (eepromFile.IsFile()) { - commands.push('-FileData=\"' + eepromFile.path + '\"'); - } else { - commands.push('-no_progData'); + // not verify + commands.push('-no_verif'); + + const binFile = this.toAbsolute(programs[0].path); + if (binFile.IsFile()) { + commands.push('-FileProg=\"' + binFile.path + '\"'); + } else { + commands.push('-no_progProg'); + } + + const eepromFile = this.toAbsolute(options.eepromFile); + if (eepromFile.IsFile()) { + commands.push('-FileData=\"' + eepromFile.path + '\"'); + } else { + commands.push('-no_progData'); + } + + const opFile = this.toAbsolute(options.optionByteFile); + if (opFile.IsFile()) { + commands.push('-FileOption=\"' + opFile.path + '\"'); + } else { + commands.push('-no_progOption'); + } } - const opFile = this.toAbsolute(options.optionByteFile); - if (opFile.IsFile()) { - commands.push('-FileOption=\"' + opFile.path + '\"'); - } else { - commands.push('-no_progOption'); + // erase all + else { + commands.push('-erase'); } return { @@ -677,10 +794,27 @@ class PyOCDUploader extends HexUploader { toolType: HexUploaderType = 'pyOCD'; - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const commandLines: string[] = []; + const option = this.getUploadOptions(); + const programs = this.parseProgramFiles(option); + + // program + if (!eraseAll) { + + if (programs.length == 0) { + throw new Error(`no any program files !`); + } + + commandLines.push('flash'); + } + + // erase all + else { + commandLines.push('erase', '--chip'); + } if (option.config) { const confFile = new File(this.project.ToAbsolutePath(option.config)); @@ -690,11 +824,6 @@ class PyOCDUploader extends HexUploader { } } - if (/\.bin$/i.test(option.bin)) { - commandLines.push('-a'); - commandLines.push(option.baseAddr || '0x08000000'); - } - // target name commandLines.push('-t'); commandLines.push(option.targetName); @@ -706,7 +835,16 @@ class PyOCDUploader extends HexUploader { } // file path - commandLines.push(option.bin); + if (!eraseAll) { + programs.forEach((file) => { + if (/\.bin$/i.test(file.path)) { + const baseAddr = option.baseAddr || programs[0].addr || '0x08000000'; + commandLines.push(`${file.path}@${baseAddr}`); + } else { + commandLines.push(file.path); + } + }); + } return { isOk: true, @@ -716,7 +854,7 @@ class PyOCDUploader extends HexUploader { protected _launch(commands: string[]): void { - const commandLine: string = 'python -m pyocd flash ' + commands.map((line) => { + const commandLine: string = 'python -m pyocd ' + commands.map((line) => { return CmdLineHandler.quoteString(line, '"'); }).join(' '); @@ -741,7 +879,7 @@ class OpenOCDUploader extends HexUploader { toolType: HexUploaderType = 'OpenOCD'; - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const exe = new File(SettingManager.GetInstance().getOpenOCDExePath()); if (!exe.IsFile()) { @@ -750,7 +888,12 @@ class OpenOCDUploader extends HexUploader { } const option = this.getUploadOptions(); - const addrStr = /\.bin$/i.test(option.bin) ? (option.baseAddr || '0x08000000') : ''; + const programs = this.parseProgramFiles(option); + + if (programs.length == 0) { + throw new Error(`no any program files !`); + } + const commands: string[] = []; const interfaceFileName = option.interface.startsWith('${workspaceFolder}/') @@ -770,10 +913,20 @@ class OpenOCDUploader extends HexUploader { `-f ${interfaceFileName}.cfg`, `-f ${targetFileName}.cfg`, `-c "init"`, - `-c "reset init"`, - `-c "program \\"${option.bin.replace(/\\{1,}/g, '/')}\\" ${addrStr} verify reset exit"` + `-c "reset init"` ); + programs.forEach(file => { + if (/\.bin$/i.test(file.path)) { + const addrStr = option.baseAddr || file.addr || '0x08000000'; + commands.push(`-c "program \\"${file.path.replace(/\\{1,}/g, '/')}\\" ${addrStr} verify reset"`); + } else { + commands.push(`-c "program \\"${file.path.replace(/\\{1,}/g, '/')}\\" verify reset"`); + } + }); + + commands.push(`-c "exit"`); + return { isOk: true, params: commands @@ -797,9 +950,16 @@ class CustomUploader extends HexUploader { toolType: HexUploaderType = 'Custom'; - protected async _prepare(): Promise> { + protected async _prepare(eraseAll?: boolean): Promise> { const option = this.getUploadOptions(); + const programs = this.parseProgramFiles(option); + + if (programs.length == 0) { + return { + isOk: new Error(`no any program files !`) + }; + } if (option.commandLine === undefined) { return { @@ -808,10 +968,24 @@ class CustomUploader extends HexUploader { } const portList = ResManager.GetInstance().enumSerialPort(); - const commandLine = option.commandLine - .replace(/\$\{hexFile\}|\$\{binFile\}|\$\{programFile\}/ig, option.bin) + + let commandLine = option.commandLine + .replace(/\$\{hexFile\}|\$\{binFile\}|\$\{programFile\}/ig, programs[0].path) .replace(/\$\{port\}/ig, portList[0] || 'none'); + programs.forEach((file, index) => { + + commandLine = commandLine + .replace(`\${hexFile[${index}]}`, file.path) + .replace(`\${binFile[${index}]}`, file.path) + .replace(`\${programFile[${index}]}`, file.path); + + if (file.addr) { + commandLine = commandLine + .replace(`\${binAddr[${index}]}`, file.addr || '0x00000000') + } + }); + return { isOk: true, params: commandLine diff --git a/src/KeilXmlParser.ts b/src/KeilXmlParser.ts index 18675211..78d8800d 100644 --- a/src/KeilXmlParser.ts +++ b/src/KeilXmlParser.ts @@ -181,7 +181,7 @@ export abstract class KeilParser { // example: file: 'c:\aa\bb', path: '../cc/f.txt', result: 'c:\aa\cc\f.txt' protected ToAbsolutePath(path: string): string { - if (NodePath.isAbsolute(path)) { + if (File.isAbsolute(path)) { return path.replace(/\//g, '\\'); } else { return NodePath.normalize(this._file.dir + File.sep + path); diff --git a/src/OperationExplorer.ts b/src/OperationExplorer.ts index a0893b95..510abf77 100644 --- a/src/OperationExplorer.ts +++ b/src/OperationExplorer.ts @@ -37,7 +37,12 @@ import { view_str$operation$create_from_internal_temp, view_str$operation$empty_8bit_prj, view_str$operation$empty_cortex_prj, - import_project_hit, view_str$import_project, view_str$operation$import_sel_out_folder, view_str$operation$empty_riscv_prj, view_str$operation$create_from_remote_repo, view_str$operation$create_from_local_disk, view_str$operation$create_empty_project_detail, view_str$operation$create_from_internal_temp_detail, view_str$operation$create_from_local_disk_detail, view_str$operation$create_from_remote_repo_detail, view_str$operation$openSettings, view_str$prompt$select_file, view_str$prompt$select_folder, view_str$prompt$select_tool_install_mode, view_str$prompt$tool_install_mode_online, view_str$prompt$tool_install_mode_local + import_project_hit, view_str$import_project, view_str$operation$import_sel_out_folder, view_str$operation$empty_riscv_prj, + view_str$operation$create_from_remote_repo, view_str$operation$create_from_local_disk, view_str$operation$create_empty_project_detail, + view_str$operation$create_from_internal_temp_detail, view_str$operation$create_from_local_disk_detail, + view_str$operation$create_from_remote_repo_detail, view_str$operation$openSettings, + view_str$prompt$select_file, view_str$prompt$select_folder, view_str$prompt$select_file_or_folder, view_str$prompt$select_tool_install_mode, + view_str$prompt$tool_install_mode_online, view_str$prompt$tool_install_mode_local } from './StringTable'; import { CreateOptions, ImportOptions, ProjectType } from './EIDETypeDefine'; import { File } from '../lib/node-utility/File'; @@ -55,6 +60,7 @@ import * as vscode from 'vscode'; import * as NodePath from 'path'; import { append2SysEnv } from './Platform'; import { ResInstaller } from './ResInstaller'; +import { AbstractProject } from './EIDEProject'; interface TemplatePickItem extends vscode.QuickPickItem, TemplateInfo { cacheFileName: string | undefined; @@ -195,7 +201,7 @@ export class OperationExplorer { label: create_project, command: { title: create_project, - command: 'Operation.Create' + command: '_cl.eide.Operation.Create' }, tooltip: create_project_hit, iconPath: { @@ -209,7 +215,7 @@ export class OperationExplorer { label: view_str$import_project, command: { title: view_str$import_project, - command: 'Operation.Import' + command: '_cl.eide.Operation.Import' }, tooltip: import_project_hit, iconPath: { @@ -223,7 +229,7 @@ export class OperationExplorer { label: open_project, command: { title: open_project, - command: 'Operation.Open' + command: '_cl.eide.Operation.Open' }, tooltip: open_project_hit, iconPath: { @@ -237,7 +243,7 @@ export class OperationExplorer { label: view_str$operation$open_serialport, command: { title: view_str$operation$open_serialport, - command: 'Operation.OpenSerialPortMonitor' + command: '_cl.eide.Operation.OpenSerialPortMonitor' }, tooltip: view_str$operation$open_serialport, iconPath: { @@ -256,7 +262,7 @@ export class OperationExplorer { label: view_str$operation$setToolchainPath, command: { title: view_str$operation$setToolchainPath, - command: 'Operation.SetToolchainPath' + command: '_cl.eide.Operation.SetToolchainPath' }, tooltip: view_str$operation$setToolchainPath, iconPath: { @@ -273,7 +279,7 @@ export class OperationExplorer { label: view_str$operation$openSettings, command: { title: view_str$operation$openSettings, - command: 'Operation.openSettings' + command: '_cl.eide.Operation.openSettings' }, tooltip: view_str$operation$openSettings, iconPath: { @@ -285,17 +291,6 @@ export class OperationExplorer { this.provider.Update(); } - private validateProjectName(value: string): string | undefined { - - if (value.trim() === '') { - return view_str$operation$name_can_not_be_blank; - } - - if (/&|<|>|\(|\)|@|\^|\|/.test(value)) { - return view_str$operation$name_can_not_have_invalid_char; - } - } - //--------------- event ----------------- OnOpenProject() { @@ -453,7 +448,7 @@ export class OperationExplorer { const name = await vscode.window.showInputBox({ placeHolder: input_project_name, ignoreFocusOut: true, - validateInput: (name) => this.validateProjectName(name) + validateInput: (name) => AbstractProject.validateProjectName(name) }); if (name === undefined) { return; @@ -560,11 +555,23 @@ export class OperationExplorer { detail: view_str$operation$setKeil51Path }, { - label: 'ARMCC', + label: 'MDK', type: 'AC5', - description: this.getStatusTxt(toolchainManager.isToolchainPathReady('AC5')), + description: this.getStatusTxt(settingManager.isMDKIniReady()), detail: view_str$operation$setMDKPath }, + { + label: 'ARMCC V5', + type: 'AC5', + description: this.getStatusTxt(toolchainManager.isToolchainPathReady('AC5')), + detail: view_str$operation$setToolchainInstallDir.replace('${name}', 'ARMCC V5 Toolchain') + }, + { + label: 'ARMCC V6', + type: 'AC6', + description: this.getStatusTxt(toolchainManager.isToolchainPathReady('AC6')), + detail: view_str$operation$setToolchainInstallDir.replace('${name}', 'ARMCC V6 Toolchain') + }, { label: 'GNU Arm Embedded Toolchain', type: 'GCC', @@ -640,27 +647,23 @@ export class OperationExplorer { let dialogOption: vscode.OpenDialogOptions; - switch (item.type) { - case 'AC5': - case 'Keil_C51': - dialogOption = { - openLabel: view_str$prompt$select_file, - filters: { - "TOOLS.INI file": ['INI'] - }, - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false - }; - break; - default: - dialogOption = { - openLabel: view_str$prompt$select_folder, - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false - }; - break; + if (item.type == 'Keil_C51' || item.label == 'MDK') { + dialogOption = { + openLabel: view_str$prompt$select_file, + filters: { + "TOOLS.INI file": ['INI'] + }, + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false + }; + } else { + dialogOption = { + openLabel: view_str$prompt$select_folder, + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false + }; } const path = await vscode.window.showOpenDialog(dialogOption); @@ -670,21 +673,21 @@ export class OperationExplorer { const tcManager = ToolchainManager.getInstance(); - switch (item.type) { - case 'AC5': - settingManager.SetARMINIPath(path[0].fsPath); - break; - case 'Keil_C51': - settingManager.SetC51INIPath(path[0].fsPath); - break; - default: - const iToolchian = tcManager.getToolchainByName(item.type); - if (iToolchian) { - vscode.workspace.getConfiguration().update( - iToolchian.settingName, path[0].fsPath, vscode.ConfigurationTarget.Global - ); - } - break; + if (item.type == 'Keil_C51') { + settingManager.SetC51INIPath(path[0].fsPath); + } + + else if (item.label == 'MDK') { + settingManager.SetMdkINIPath(path[0].fsPath); + } + + else { + const iToolchian = tcManager.getToolchainByName(item.type); + if (iToolchian) { + vscode.workspace.getConfiguration().update( + iToolchian.settingName, path[0].fsPath, vscode.ConfigurationTarget.Global + ); + } } } @@ -693,8 +696,8 @@ export class OperationExplorer { let prevGroupStack: TemplateGroup[] = []; let curTempGroup: TemplateGroup = templateGroup; - const goBackItemForGroup: vscode.QuickPickItem = { label: '..', description: 'Back' }; - const goBackItemForItem: vscode.QuickPickItem = { label: '..', detail: 'Back' }; + const goBackItemForGroup: vscode.QuickPickItem = { label: '..', description: 'Back', alwaysShow: true }; + const goBackItemForItem: vscode.QuickPickItem = { label: '..', detail: 'Back', alwaysShow: true }; // selection loop while (true) { @@ -812,7 +815,7 @@ export class OperationExplorer { const res = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: 'searching from ' + hostName + ' ...', + title: 'Searching from ' + hostName + ' ...', cancellable: true }, (_, token): Thenable> => { return new Promise(async (resolve) => { @@ -1079,7 +1082,7 @@ export class OperationExplorer { const name = await vscode.window.showInputBox({ placeHolder: input_project_name, ignoreFocusOut: true, - validateInput: (name) => this.validateProjectName(name) + validateInput: (name) => AbstractProject.validateProjectName(name) }); if (name === undefined) { @@ -1152,7 +1155,7 @@ export class OperationExplorer { const name = await vscode.window.showInputBox({ placeHolder: input_project_name, ignoreFocusOut: true, - validateInput: (name) => this.validateProjectName(name) + validateInput: (name) => AbstractProject.validateProjectName(name) }); if (name === undefined) { diff --git a/src/Platform.ts b/src/Platform.ts index 3f547787..063406e0 100644 --- a/src/Platform.ts +++ b/src/Platform.ts @@ -80,7 +80,7 @@ export function find(fileName: string): string | undefined { { windowsHide: true, encoding: 'ascii', shell: 'cmd' }).split(/\r\n|\n/); if (nameList.length > 0) { const path = nameList[0].replace(/"/g, ''); - if (NodePath.isAbsolute(path)) { + if (File.isAbsolute(path)) { return path; } } diff --git a/src/ResManager.ts b/src/ResManager.ts index 3c70dc01..d148fb70 100644 --- a/src/ResManager.ts +++ b/src/ResManager.ts @@ -40,6 +40,7 @@ import { AbstractProject } from "./EIDEProject"; import { SettingManager } from "./SettingManager"; import * as utility from './utility' import { CmdLineHandler } from "./CmdLineHandler"; +import * as yaml from 'yaml'; let resManager: ResManager | undefined; @@ -90,7 +91,6 @@ export class ResManager extends events.EventEmitter { private cacheInfoList: FileCacheInfo[]; private stm8DevList: string[]; - private appConfigFile: File; private appConfig: any; private constructor(context?: vscode.ExtensionContext) { @@ -110,7 +110,6 @@ export class ResManager extends events.EventEmitter { } this.LoadSysEnv(); - this.appConfigFile = File.fromArray([this.GetAppDataDir().path, 'config.json']); this.InitIcons(); this.LoadAppConfig(); @@ -124,7 +123,6 @@ export class ResManager extends events.EventEmitter { GlobalEvent.on('extension_close', () => { this.saveCache(); - this.saveAppConfig(); }); } @@ -140,22 +138,6 @@ export class ResManager extends events.EventEmitter { return codePage; } - static getGithubHash(f: File | Buffer): string { - if (f instanceof File) { - const header = Buffer.from('blob ' + f.getSize() + '\0'); - const buf = Buffer.concat([header, fs.readFileSync(f.path)], header.length + f.getSize()); - const hash = crypto.createHash('sha1'); - hash.update(buf); - return hash.digest('hex'); - } else { - const header = Buffer.from('blob ' + f.length + '\0'); - const buf = Buffer.concat([header, f], header.length + f.length); - const hash = crypto.createHash('sha1'); - hash.update(buf); - return hash.digest('hex'); - } - } - static getAppFullName(): string { return appName; } @@ -226,12 +208,14 @@ export class ResManager extends events.EventEmitter { //==================== private LoadAppConfig() { - if (this.appConfigFile.IsFile()) { + + const cfgFile = File.fromArray([this.GetAppDataDir().path, 'config.yaml']); + + if (cfgFile.IsFile()) { try { - this.appConfig = JSON.parse(this.appConfigFile.Read()); + this.appConfig = yaml.parse(cfgFile.Read()); } catch (error) { - this.appConfig = Object.create(null); - console.error(error); + GlobalEvent.emit('msg', ExceptionToMessage(error, 'Hidden')); } } } @@ -240,13 +224,6 @@ export class ResManager extends events.EventEmitter { return this.appConfig; } - saveAppConfig(newConfig?: any) { - if (typeof newConfig === 'object') { - this.appConfig = newConfig; - } - this.appConfigFile.Write(JSON.stringify(this.appConfig, undefined, 4)); - } - //===================== getCache(name: string): FileCacheInfo | undefined { @@ -332,6 +309,10 @@ export class ResManager extends events.EventEmitter { } } + getEideHomeFolder(): File { + return File.fromArray([os.homedir(), '.eide']); + } + GetLogDir(): File { const logDir = this.GetDir('log'); if (logDir === undefined) { @@ -495,7 +476,8 @@ export class ResManager extends events.EventEmitter { /* gen jlink internal device list to file */ try { fs.unlinkSync(devXmlFile.path) } catch (error) { /* do nothing */ } - try { ChildProcess.execSync(cmd); } catch (error) { /* do nothing */ } + try { ChildProcess.execSync(cmd) } catch (error) { /* do nothing */ } + try { fs.unlinkSync(jlinkTmpCmdFile.path) } catch (error) { /* do nothing */ } // rm tmp file if (devXmlFile.IsFile()) { file = devXmlFile; } try { @@ -511,6 +493,11 @@ export class ResManager extends events.EventEmitter { const dom = parser.xml2js(file.Read()); + // rm tmp file + if (devXmlFile.IsFile()) { + try { fs.unlinkSync(devXmlFile.path) } catch (error) { } + } + if (dom.DeviceDatabase == undefined) { throw Error(`Not found 'DeviceDatabase' in devices xml, [file]: '${file.path}'`); } diff --git a/src/SettingManager.ts b/src/SettingManager.ts index dddea1ba..93ac3c99 100644 --- a/src/SettingManager.ts +++ b/src/SettingManager.ts @@ -51,7 +51,7 @@ export interface PortSerialOption { let _instance: SettingManager | undefined; -export interface INIStatus { +interface INIStatus { [name: string]: boolean; } @@ -69,7 +69,7 @@ export class SettingManager { private eideEnv: Map; private _checkStatus: INIStatus = { - 'ARM': false, + 'MDK': false, 'C51': false }; @@ -242,6 +242,18 @@ export class SettingManager { //--------------------- Global Option ------------------------ + isShowOutputFilesInExplorer(): boolean { + return this.getConfiguration().get('Option.ShowOutputFilesInExplorer') || false; + } + + getBuilderAdditionalCommandLine(): string | undefined { + return this.getConfiguration().get('Builder.AdditionalCommandLine'); + } + + getMapViewParserDepth(): number { + return this.getConfiguration().get('Option.MapViewParserDepth') || 1; + } + isDisplaySourceRefs(): boolean { return this.getConfiguration().get('Option.ShowSourceReferences') || false; } @@ -255,20 +267,29 @@ export class SettingManager { } getGithubRepositoryUrl(): string { - return (this.getConfiguration().get('Template.Repository.Url') || 'null') + return (this.getConfiguration().get('Repository.Template.Url') || 'null') + .trim().replace(/^https:\/\//i, ''); + } + + getCmsisPackRepositoryUrl(): string { + return (this.getConfiguration().get('Repository.CmsisPack.Url') || 'null') .trim().replace(/^https:\/\//i, ''); } isUseGithubProxy(): boolean { - return this.getConfiguration().get('Template.Repository.UseProxy') || false; + return this.getConfiguration().get('Repository.UseProxy') || false; } isUseTaskToBuild(): boolean { return this.getConfiguration().get('Option.UseTaskToBuild') || false; } - getINIStatus(): INIStatus { - return JSON.parse(JSON.stringify(this._checkStatus)); + isKeilC51IniReady(): boolean { + return this._checkStatus['C51']; + } + + isMDKIniReady(): boolean { + return this._checkStatus['MDK']; } isInsertCommandsAtBegin(): boolean { @@ -391,61 +412,99 @@ export class SettingManager { return new File(this.getGccFolderFromConfig('ARM.GCC.InstallDirectory', execName) || 'null'); } - SetARMINIPath(path: string) { + // --- + + getArmcc5Dir(): File { + return new File( + this.getGccFolderFromConfig('ARM.ARMCC5.InstallDirectory', 'armcc') || + this.pathCache.get('ARMCC5') || + 'null' + ); + } + + getArmcc6Dir(): File { + return new File( + this.getGccFolderFromConfig('ARM.ARMCC6.InstallDirectory', 'armclang') || + this.pathCache.get('ARMCC6') || + 'null' + ); + } + + SetMdkINIPath(path: string) { this.setConfigValue('ARM.INI.Path', path); } - private GetARMINIFile(): File { + private GetMdkINIFile(): File { const iniPath = this.getConfiguration().get('ARM.INI.Path') || ''; return new File(Utility.formatPath(iniPath)); } - GetMdkDir(): File { - return new File(this.pathCache.get('MDK') || 'null'); + GetMdkArmDir(): File | undefined { + const path = this.pathCache.get('MDK'); + if (path) { + return new File(path); + } } private refreshMDKStatus(): void { - /* parse from env */ - if (process.env && process.env['Keil_Root']) { - const rootFolder = Utility.formatPath(process.env['Keil_Root']); - const armFolder = File.fromArray([rootFolder, 'ARM']); - if (armFolder.IsDir() && - File.fromArray([armFolder.path, 'ARMCC']).IsDir()) { - this.pathCache.set('MDK', armFolder.path); - this._checkStatus['ARM'] = true; - return; /* found it, exit */ - } - } - /* parse from ini file */ - const iniFile = this.GetARMINIFile(); + const iniFile = this.GetMdkINIFile(); if (iniFile.IsFile()) { try { const iniData = ini.parse(iniFile.Read()); // check ini file fields if (iniData["ARM"] && iniData["ARM"]["PATH"]) { - const binDir = new File(formatPath((iniData["ARM"]["PATH"]).replace(/"/g, ''))); - // update and check - this.pathCache.set('MDK', binDir.path); - const cDir = File.fromArray([binDir.path, 'ARMCC']); - if (!cDir.IsDir()) { throw new Error('Not found folder, [path]: ' + cDir.path); } - this._checkStatus['ARM'] = true; + + // mdk ARM dir + const mdkArmDir = new File(formatPath((iniData["ARM"]["PATH"]).replace(/"/g, ''))); + + // cache arm dir + if (mdkArmDir.IsDir()) { + this.pathCache.set('MDK', mdkArmDir.path); + } + + // cache armcc5 path + const armcc5Dir = File.fromArray([mdkArmDir.path, 'ARMCC', 'bin']); + if (!armcc5Dir.IsDir()) { throw new Error('Not found folder, [path]: ' + armcc5Dir.path); } + this.pathCache.set('ARMCC5', armcc5Dir.dir); + + // update status + this._checkStatus['MDK'] = true; + + // cache armcc6 path + const armcc6Dir = File.fromArray([mdkArmDir.path, 'ARMCLANG', 'bin']); + if (armcc6Dir.IsDir()) { this.pathCache.set('ARMCC6', armcc6Dir.dir);; } + return; // mdk path is valid, return it } // invalid ini file else { - this.pathCache.delete('MDK'); + this.pathCache.delete('ARMCC5'); + this.pathCache.delete('ARMCC6'); throw new Error('Invalid ARM INI file, [path] : ' + iniFile.path); } + } catch (error) { GlobalEvent.emit('globalLog', ExceptionToMessage(error, 'Hidden')); } } - this._checkStatus['ARM'] = false; + /* parse from env */ + if (process.env && process.env['Keil_Root']) { + const rootFolder = Utility.formatPath(process.env['Keil_Root']); + const armFolder = File.fromArray([rootFolder, 'ARM']); + if (armFolder.IsDir() && + File.fromArray([armFolder.path, 'ARMCC']).IsDir()) { + this.pathCache.set('MDK', armFolder.path); + this._checkStatus['MDK'] = true; + return; /* found it, exit */ + } + } + + this._checkStatus['MDK'] = false; } //------------------------------- SDCC -------------------------------- @@ -486,18 +545,6 @@ export class SettingManager { private refreshC51Status(): void { - /* parse from env */ - if (process.env && process.env['Keil_Root']) { - const rootFolder = Utility.formatPath(process.env['Keil_Root']); - const c51Folder = File.fromArray([rootFolder, 'C51']); - if (c51Folder.IsDir() && - File.fromArray([c51Folder.path, 'BIN']).IsDir()) { - this.pathCache.set('C51', c51Folder.path); - this._checkStatus['C51'] = true; - return; /* found it, exit */ - } - } - /* parse from ini file */ const iniFile = this.GetC51INIFile(); if (iniFile.IsFile()) { @@ -525,6 +572,18 @@ export class SettingManager { } } + /* parse from env */ + if (process.env && process.env['Keil_Root']) { + const rootFolder = Utility.formatPath(process.env['Keil_Root']); + const c51Folder = File.fromArray([rootFolder, 'C51']); + if (c51Folder.IsDir() && + File.fromArray([c51Folder.path, 'BIN']).IsDir()) { + this.pathCache.set('C51', c51Folder.path); + this._checkStatus['C51'] = true; + return; /* found it, exit */ + } + } + this._checkStatus['C51'] = false; } } \ No newline at end of file diff --git a/src/StringTable.ts b/src/StringTable.ts index bf3fb072..695a92b1 100644 --- a/src/StringTable.ts +++ b/src/StringTable.ts @@ -210,8 +210,13 @@ export const view_str$project$sel_folder_type = [ ][langIndex]; export const view_str$project$folder_type_fs = [ - '文件系统文件夹', - 'Filesystem Folder' + '普通文件夹', + 'Normal Folder' +][langIndex]; + +export const view_str$project$folder_type_fs_desc = [ + '从磁盘中选择一个文件夹,eide 将其直接链接到项目中,并自动搜索和添加其中的源文件以及包含路径', + 'Select a folder from disk, eide will link it directly into your project, and automatically search and add source files and include paths to it.' ][langIndex]; export const view_str$project$folder_type_virtual = [ @@ -219,6 +224,11 @@ export const view_str$project$folder_type_virtual = [ 'Virtual Folder' ][langIndex]; +export const view_str$project$folder_type_virtual_desc = [ + '新建一个虚拟文件夹(该文件夹在磁盘上是不存在的)来组织源文件的结构', + 'Create a VirtualFolder (The folder does not exist on disk) to organize the source file structure.' +][langIndex]; + export const view_str$project$add_source = [ '添加源文件', 'Add Source Files' @@ -230,8 +240,8 @@ export const view_str$project$sel_target = [ ][langIndex]; export const view_str$project$other_settings = [ - '其他设置', - 'Other Settings' + '项目设置', + 'Project Settings' ][langIndex]; export const view_str$project$cmsis_components = [ @@ -270,7 +280,7 @@ export const include_desc = [ ][langIndex]; export const lib_desc = [ - '库目录', + '链接库目录', 'Library Directories' ][langIndex]; @@ -280,13 +290,13 @@ export const source_dir_desc = [ ][langIndex]; export const definition_list_desc = [ - '宏定义', - 'Macro List' + '预处理器定义', + 'Preprocessor Definitions' ][langIndex]; export const project_dependence = [ - '项目依赖', - 'Project Dependences' + '项目属性', + 'Project Attributes' ][langIndex]; export const view_str$dialog$add_to_source_folder = [ @@ -426,6 +436,11 @@ export const view_str$prompt$select_folder = [ `Select This Folder` ][langIndex]; +export const view_str$prompt$select_file_or_folder = [ + `选择该文件或文件夹`, + `Select This File Or Folder` +][langIndex]; + export const view_str$prompt$not_found_compiler = [ `无法找到编译器 '{}' 的安装位置 !`, `Not found the compiler '{}' installation location !` @@ -662,13 +677,13 @@ export const view_str$operation$baudrate = [ ][langIndex]; export const view_str$operation$setKeil51Path = [ - '设置 Keil_C51 工具链的 TOOLS.INI 路径', - 'Set Keil_C51 toolchain\'s \'TOOLS.INI\' path' + '设置 Keil_C51 的 TOOLS.INI 路径', + 'Set Keil_C51\'s \'TOOLS.INI\' path' ][langIndex]; export const view_str$operation$setMDKPath = [ - '设置 ARMCC 工具链的 TOOLS.INI 路径', - 'Set ARMCC Toolchain\'s \'TOOLS.INI\' path' + '设置 MDK 的 TOOLS.INI 路径', + 'Set MDK\'s \'TOOLS.INI\' path' ][langIndex]; export const view_str$operation$setToolchainInstallDir = [ diff --git a/src/ToolchainManager.ts b/src/ToolchainManager.ts index 4c62064d..d48dc7eb 100644 --- a/src/ToolchainManager.ts +++ b/src/ToolchainManager.ts @@ -323,10 +323,11 @@ export class ToolchainManager { switch (name) { case 'AC5': + return File.fromArray([settingManager.getArmcc5Dir().path, 'bin']).IsDir(); case 'AC6': - return settingManager.getINIStatus()['ARM']; + return File.fromArray([settingManager.getArmcc6Dir().path, 'bin']).IsDir(); case 'Keil_C51': - return settingManager.getINIStatus()['C51']; + return settingManager.isKeilC51IniReady(); case 'GCC': return File.fromArray([settingManager.getGCCDir().path, 'bin']).IsDir(); case 'IAR_STM8': @@ -910,7 +911,7 @@ class AC5 implements IToolchian { readonly version = 4; - readonly settingName: string = 'EIDE.ARM.INI.Path'; + readonly settingName: string = 'EIDE.ARM.ARMCC5.InstallDirectory'; readonly categoryName: string = 'ARMCC'; @@ -934,7 +935,7 @@ class AC5 implements IToolchian { } getToolchainDir(): File { - return SettingManager.GetInstance().GetMdkDir(); + return SettingManager.GetInstance().getArmcc5Dir(); } getForceIncludeHeaders(): string[] { @@ -952,7 +953,7 @@ class AC5 implements IToolchian { } getSystemIncludeList(builderOpts: ICompileOptions): string[] { - const incDir = new File(this.getToolchainDir().path + File.sep + 'ARMCC' + File.sep + 'include'); + const incDir = File.fromArray([this.getToolchainDir().path, 'include']); if (incDir.IsDir()) { return [incDir].concat(incDir.GetList(File.EMPTY_FILTER)).map((f) => { return f.path; }); } @@ -964,7 +965,7 @@ class AC5 implements IToolchian { } getLibDirs(): string[] { - return [File.fromArray([this.getToolchainDir().path, 'ARMCC', 'lib']).path]; + return [File.fromArray([this.getToolchainDir().path, 'lib']).path]; } getDefaultConfig(): ICompileOptions { @@ -995,7 +996,7 @@ class AC6 implements IToolchian { readonly version = 3; - readonly settingName: string = 'EIDE.ARM.INI.Path'; + readonly settingName: string = 'EIDE.ARM.ARMCC6.InstallDirectory'; readonly categoryName: string = 'ARMCC'; @@ -1007,10 +1008,11 @@ class AC6 implements IToolchian { readonly verifyFileName: string = 'arm.v6.verify.json'; + /* private readonly defMacroList: string[]; constructor() { - const armClang = File.fromArray([this.getToolchainDir().path, 'ARMCLANG', 'bin', 'armclang.exe']); + const armClang = File.fromArray([this.getToolchainDir().path, 'bin', 'armclang.exe']); this.defMacroList = this.getMacroList(armClang.path); } @@ -1037,6 +1039,7 @@ class AC6 implements IToolchian { return ['__GNUC__=4', '__GNUC_MINOR__=2', '__GNUC_PATCHLEVEL__=1']; } } + */ newInstance(): IToolchian { return new AC6(); @@ -1060,11 +1063,11 @@ class AC6 implements IToolchian { } getToolchainDir(): File { - return SettingManager.GetInstance().GetMdkDir(); + return SettingManager.GetInstance().getArmcc6Dir(); } getInternalDefines(builderOpts: ICompileOptions): string[] { - return this.defMacroList; + return []; } getCustomDefines(): string[] | undefined { @@ -1073,8 +1076,8 @@ class AC6 implements IToolchian { getSystemIncludeList(builderOpts: ICompileOptions): string[] { return [ - File.fromArray([this.getToolchainDir().path, 'ARMCLANG', 'include']).path, - File.fromArray([this.getToolchainDir().path, 'ARMCLANG', 'include', 'libcxx']).path + File.fromArray([this.getToolchainDir().path, 'include']).path, + File.fromArray([this.getToolchainDir().path, 'include', 'libcxx']).path ]; } @@ -1089,7 +1092,7 @@ class AC6 implements IToolchian { } getLibDirs(): string[] { - return [File.fromArray([this.getToolchainDir().path, 'ARMCLANG', 'lib']).path]; + return [File.fromArray([this.getToolchainDir().path, 'lib']).path]; } getDefaultConfig(): ICompileOptions { @@ -1315,9 +1318,9 @@ class IARSTM8 implements IToolchian { options.linker['linker-config'] = `"${prjInfo.toAbsolutePath(options.linker['linker-config'])}"`; } - // use toolchain root folder, like ${ToolDir}\stm8\config - else if (linkerConfig.toLowerCase().startsWith('${tooldir}')) { - const absPath = (options.linker['linker-config']).replace(/\$\{ToolDir\}/i, this.getToolchainDir().path); + // use toolchain root folder, like ${ToolchainRoot}\stm8\config + else if (linkerConfig.toLowerCase().startsWith('${ToolchainRoot}')) { + const absPath = (options.linker['linker-config']).replace(/\$\{ToolchainRoot\}/i, this.getToolchainDir().path); options.linker['linker-config'] = `"${NodePath.normalize(absPath)}"`; } } diff --git a/src/extension.ts b/src/extension.ts index 625f29a4..b4bd7103 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,25 +1,25 @@ /* - MIT License - - Copyright (c) 2019 github0null - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + MIT License + + Copyright (c) 2019 github0null + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. */ import * as vscode from 'vscode'; @@ -27,6 +27,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as unzipper from 'unzipper'; import * as NodePath from 'path'; +import * as ChildProcess from 'child_process'; import { GlobalEvent } from './GlobalEvents'; import { OperationExplorer } from './OperationExplorer'; @@ -77,97 +78,114 @@ export async function activate(context: vscode.ExtensionContext) { subscriptions.push(vscode.commands.registerCommand('eide.c51ToSdcc', () => c51ToSDCC())); subscriptions.push(vscode.commands.registerCommand('eide.ReloadJlinkDevs', () => reloadJlinkDevices())); subscriptions.push(vscode.commands.registerCommand('eide.ReloadStm8Devs', () => reloadStm8Devices())); - - // other commands subscriptions.push(vscode.commands.registerCommand('eide.selectBaudrate', () => onSelectSerialBaudrate())); + // operations const operationExplorer = new OperationExplorer(context); - subscriptions.push(vscode.commands.registerCommand('Operation.Open', () => operationExplorer.OnOpenProject())); - subscriptions.push(vscode.commands.registerCommand('Operation.Create', () => operationExplorer.OnCreateProject())); - subscriptions.push(vscode.commands.registerCommand('Operation.Import', () => operationExplorer.OnImportProject())); - subscriptions.push(vscode.commands.registerCommand('Operation.SetToolchainPath', () => operationExplorer.OnSetToolchainPath())); - subscriptions.push(vscode.commands.registerCommand('Operation.OpenSerialPortMonitor', () => operationExplorer.onOpenSerialPortMonitor())); - subscriptions.push(vscode.commands.registerCommand('Operation.openSettings', () => SettingManager.jumpToSettings('@ext:cl.eide'))); - /* export commands */ + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.Open', () => operationExplorer.OnOpenProject())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.Create', () => operationExplorer.OnCreateProject())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.Import', () => operationExplorer.OnImportProject())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.SetToolchainPath', () => operationExplorer.OnSetToolchainPath())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.OpenSerialPortMonitor', () => operationExplorer.onOpenSerialPortMonitor())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.Operation.openSettings', () => SettingManager.jumpToSettings('@ext:cl.eide'))); + + // operations user cmds subscriptions.push(vscode.commands.registerCommand('eide.operation.install_toolchain', () => operationExplorer.OnSetToolchainPath())); subscriptions.push(vscode.commands.registerCommand('eide.operation.import_project', () => operationExplorer.OnImportProject())); subscriptions.push(vscode.commands.registerCommand('eide.operation.new_project', () => operationExplorer.OnCreateProject())); + // projects projectExplorer = new ProjectExplorer(context); - subscriptions.push(vscode.commands.registerCommand('_cl.eide.workspace.build', () => projectExplorer.buildWorkspace())); subscriptions.push(vscode.commands.registerCommand('_cl.eide.workspace.rebuild', () => projectExplorer.buildWorkspace(true))); subscriptions.push(vscode.commands.registerCommand('_cl.eide.workspace.open.config', () => projectExplorer.openWorkspaceConfig())); subscriptions.push(vscode.commands.registerCommand('_cl.eide.workspace.make.template', (item) => projectExplorer.ExportToTemplate(undefined, true))); + // project user cmds subscriptions.push(vscode.commands.registerCommand('eide.project.rebuild', (item) => projectExplorer.BuildSolution(item))); subscriptions.push(vscode.commands.registerCommand('eide.project.build', (item) => projectExplorer.BuildSolution(item, { useFastMode: true }))); subscriptions.push(vscode.commands.registerCommand('eide.project.clean', (item) => projectExplorer.BuildClean(item))); subscriptions.push(vscode.commands.registerCommand('eide.project.uploadToDevice', (item) => projectExplorer.UploadToDevice(item))); subscriptions.push(vscode.commands.registerCommand('eide.reinstall.binaries', () => checkAndInstallBinaries(context, true))); - - subscriptions.push(vscode.commands.registerCommand('_project.historyRecord', () => projectExplorer.openHistoryRecords())); - subscriptions.push(vscode.commands.registerCommand('_project.clearHistoryRecord', () => projectExplorer.clearAllHistoryRecords())); - subscriptions.push(vscode.commands.registerCommand('_project.showBuildParams', (item) => projectExplorer.BuildSolution(item, { useDebug: true }))); - subscriptions.push(vscode.commands.registerCommand('_project.generate.makefile', (item) => projectExplorer.generateMakefile(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.setActive', (item) => projectExplorer.setActiveProject(item))); - subscriptions.push(vscode.commands.registerCommand('_project.close', (item) => projectExplorer.Close(item))); - subscriptions.push(vscode.commands.registerCommand('_project.saveAll', () => projectExplorer.SaveAll())); - subscriptions.push(vscode.commands.registerCommand('_project.refresh', () => projectExplorer.Refresh())); - subscriptions.push(vscode.commands.registerCommand('_project.switchMode', (item) => projectExplorer.switchTarget(item))); - subscriptions.push(vscode.commands.registerCommand('_project.exportAsTemplate', (item) => projectExplorer.ExportToTemplate(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.addSrcDir', (item) => projectExplorer.AddSrcDir(item))); - subscriptions.push(vscode.commands.registerCommand('_project.removeSrcDir', (item) => projectExplorer.RemoveSrcDir(item))); - subscriptions.push(vscode.commands.registerCommand('_project.sourceRoot.refresh', (item) => projectExplorer.refreshSrcRoot(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.source.filesystem_folder_add_file', (item) => projectExplorer.fs_folderAddFile(item))); - subscriptions.push(vscode.commands.registerCommand('_project.source.filesystem_folder_add', (item) => projectExplorer.fs_folderAdd(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.source.virtual_folder_add_file', (item) => projectExplorer.Virtual_folderAddFile(item))); - subscriptions.push(vscode.commands.registerCommand('_project.source.virtual_folder_add', (item) => projectExplorer.Virtual_folderAdd(item))); - subscriptions.push(vscode.commands.registerCommand('_project.source.virtual_folder_remove', (item) => projectExplorer.Virtual_removeFolder(item))); - subscriptions.push(vscode.commands.registerCommand('_project.source.virtual_folder_rename', (item) => projectExplorer.Virtual_renameFolder(item))); - subscriptions.push(vscode.commands.registerCommand('_project.source.virtual_file_remove', (item) => projectExplorer.Virtual_removeFile(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.excludeSource', (item) => projectExplorer.ExcludeSourceFile(item))); - subscriptions.push(vscode.commands.registerCommand('_project.unexcludeSource', (item) => projectExplorer.UnexcludeSourceFile(item))); - subscriptions.push(vscode.commands.registerCommand('_project.excludeFolder', (item) => projectExplorer.ExcludeFolder(item))); - subscriptions.push(vscode.commands.registerCommand('_project.unexcludeFolder', (item) => projectExplorer.UnexcludeFolder(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.installCMSISHeaders', (item) => projectExplorer.installCMSISHeaders(item))); - subscriptions.push(vscode.commands.registerCommand('_project.removePackage', (item) => projectExplorer.UninstallKeilPackage(item))); - subscriptions.push(vscode.commands.registerCommand('_project.addPackage', (item) => projectExplorer.InstallKeilPackage(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.exportXml', (item) => projectExplorer.ExportKeilXml(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.setDevice', (item) => projectExplorer.SetDevice(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.modifyCompileConfig', (item) => projectExplorer.ModifyCompileConfig(item))); - subscriptions.push(vscode.commands.registerCommand('_project.switchToolchain', (item) => projectExplorer.onSwitchCompileTools(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.modifyUploadConfig', (item) => projectExplorer.ModifyUploadConfig(item))); - subscriptions.push(vscode.commands.registerCommand('_project.switchUploader', (item) => projectExplorer.switchUploader(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.addIncludeDir', (item) => projectExplorer.AddIncludeDir(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.addDefine', (item) => projectExplorer.AddDefine(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.addLibDir', (item) => projectExplorer.AddLibDir(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.showIncludeDir', (item) => projectExplorer.showIncludeDir(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.showDefine', (item) => projectExplorer.showDefine(item.val.projectIndex))); - subscriptions.push(vscode.commands.registerCommand('_project.showLibDir', (item) => projectExplorer.showLibDir(item.val.projectIndex))); - - subscriptions.push(vscode.commands.registerCommand('_project.modifyOtherSettings', (item) => projectExplorer.ModifyOtherSettings(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.removeDependenceItem', (item) => projectExplorer.RemoveDependenceItem(item))); - subscriptions.push(vscode.commands.registerCommand('_project.importDependenceFromPack', (item) => projectExplorer.ImportPackageDependence(item))); - subscriptions.push(vscode.commands.registerCommand('_project.removeDependenceFromPack', (item) => projectExplorer.RemovePackageDependence(item))); - - subscriptions.push(vscode.commands.registerCommand('_project.copyItemValue', (item) => projectExplorer.CopyItemValue(item))); - - /* other project tools */ - subscriptions.push(vscode.commands.registerCommand('_project.source.show_disassembly', (url) => projectExplorer.showDisassembly(url))); - subscriptions.push(vscode.commands.registerCommand('_project.source.show_cmsis_config_wizard', (url) => projectExplorer.showCmsisConfigWizard(url))); - //subscriptions.push(vscode.commands.registerCommand('_project.cppcheck.check_file', (url) => projectExplorer.cppcheckFile(url))); - subscriptions.push(vscode.commands.registerCommand('_project.cppcheck.check_all', (item) => projectExplorer.cppcheckProject(item))); - subscriptions.push(vscode.commands.registerCommand('_project.cppcheck.clear_all', (item) => projectExplorer.clearCppcheckDiagnostic())); + subscriptions.push(vscode.commands.registerCommand('eide.project.flash.erase.all', (item) => projectExplorer.UploadToDevice(item, true))); + + // operations bar + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.historyRecord', () => projectExplorer.openHistoryRecords())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.clearHistoryRecord', () => projectExplorer.clearAllHistoryRecords())); + + // project + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.showBuildParams', (item) => projectExplorer.BuildSolution(item, { useDebug: true }))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.generate.makefile', (item) => projectExplorer.generateMakefile(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.setActive', (item) => projectExplorer.setActiveProject(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.close', (item) => projectExplorer.Close(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.saveAll', () => projectExplorer.SaveAll())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.refresh', () => projectExplorer.Refresh())); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.switchMode', (item) => projectExplorer.switchTarget(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.exportAsTemplate', (item) => projectExplorer.ExportToTemplate(item))); + + // project explorer + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.addSrcDir', (item) => projectExplorer.AddSrcDir(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.removeSrcDir', (item) => projectExplorer.RemoveSrcDir(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.sourceRoot.refresh', (item) => projectExplorer.refreshSrcRoot(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.modify.files.options', (item) => projectExplorer.showFilesOptions(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.import.ext.source.struct', (item) => projectExplorer.ImportSourceFromExtProject(item))); + + // filesystem files + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.filesystem_folder_add_file', (item) => projectExplorer.fs_folderAddFile(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.filesystem_folder_add', (item) => projectExplorer.fs_folderAdd(item))); + + // virtual files + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.virtual_folder_add_file', (item) => projectExplorer.Virtual_folderAddFile(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.virtual_folder_add', (item) => projectExplorer.Virtual_folderAdd(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.virtual_folder_remove', (item) => projectExplorer.Virtual_removeFolder(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.virtual_folder_rename', (item) => projectExplorer.Virtual_renameFolder(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.virtual_file_remove', (item) => projectExplorer.Virtual_removeFile(item))); + + // file other operations + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.excludeSource', (item) => projectExplorer.ExcludeSourceFile(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.unexcludeSource', (item) => projectExplorer.UnexcludeSourceFile(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.excludeFolder', (item) => projectExplorer.ExcludeFolder(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.unexcludeFolder', (item) => projectExplorer.UnexcludeFolder(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.file.show.dir', (item) => projectExplorer.showFileInExplorer(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.modify.path', (item) => projectExplorer.modifySourcesPath(item))); + + // package + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.installCMSISHeaders', (item) => projectExplorer.installCMSISHeaders(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.removePackage', (item) => projectExplorer.UninstallKeilPackage(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.addPackage', (item) => projectExplorer.InstallKeilPackage(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.exportXml', (item) => projectExplorer.ExportKeilXml(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.setDevice', (item) => projectExplorer.SetDevice(item.val.projectIndex))); + + // builder + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.modifyCompileConfig', (item) => projectExplorer.ModifyCompileConfig(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.switchToolchain', (item) => projectExplorer.onSwitchCompileTools(item))); + + // flasher + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.modifyUploadConfig', (item) => projectExplorer.ModifyUploadConfig(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.switchUploader', (item) => projectExplorer.switchUploader(item))); + + // project deps + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.addIncludeDir', (item) => projectExplorer.AddIncludeDir(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.addDefine', (item) => projectExplorer.AddDefine(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.addLibDir', (item) => projectExplorer.AddLibDir(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.showIncludeDir', (item) => projectExplorer.showIncludeDir(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.showDefine', (item) => projectExplorer.showDefine(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.showLibDir', (item) => projectExplorer.showLibDir(item.val.projectIndex))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.modifyOtherSettings', (item) => projectExplorer.ModifyOtherSettings(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.modify.deps', (item) => projectExplorer.ModifyCustomDependence(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.removeDependenceItem', (item) => projectExplorer.RemoveDependenceItem(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.importDependenceFromPack', (item) => projectExplorer.ImportPackageDependence(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.removeDependenceFromPack', (item) => projectExplorer.RemovePackageDependence(item))); + + // tree view global + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.copyItemValue', (item) => projectExplorer.CopyItemValue(item))); + + // other project tools + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.show_disassembly', (url) => projectExplorer.showDisassembly(url))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.source.show_cmsis_config_wizard', (url) => projectExplorer.showCmsisConfigWizard(url))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.cppcheck.check_all', (item) => projectExplorer.cppcheckProject(item))); + subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.cppcheck.clear_all', (item) => projectExplorer.clearCppcheckDiagnostic())); + //subscriptions.push(vscode.commands.registerCommand('_cl.eide.project.cppcheck.check_file', (url) => projectExplorer.cppcheckFile(url))); operationExplorer.on('request_open_project', (fsPath) => projectExplorer.emit('request_open_project', fsPath)); operationExplorer.on('request_create_project', (option) => projectExplorer.emit('request_create_project', option)); @@ -227,7 +245,7 @@ async function onSelectSerialBaudrate() { '4800', '9600', '14400', '19200', '28800', '38400', '57600', '115200', '230400', - '460800', '576000' + '460800', '576000', '921600' ]; const baudrate = await vscode.window.showQuickPick(baudList, { @@ -244,9 +262,12 @@ async function onSelectSerialBaudrate() { async function checkAndInstallBinaries(constex: vscode.ExtensionContext, forceInstall?: boolean): Promise { + const eideCfg = ResManager.GetInstance().getAppConfig(); + const binVersion = eideCfg['binaray_version'] ? `-${eideCfg['binaray_version']}` : '' + const downloadSites: string[] = [ - `https://raw.githubusercontent.com/github0null/eide-resource/master/binaries/bin.zip`, - `https://raw-github.github0null.io/github0null/eide-resource/master/binaries/bin.zip` + `https://raw.githubusercontent.com/github0null/eide-resource/master/binaries/bin${binVersion}.zip`, + `https://raw-github.github0null.io/github0null/eide-resource/master/binaries/bin${binVersion}.zip` ]; /* random select the order of site */ @@ -270,7 +291,7 @@ async function checkAndInstallBinaries(constex: vscode.ExtensionContext, forceIn let installedDone = false; try { - const tmpFile = File.fromArray([os.tmpdir(), 'eide-binaries.zip']); + const tmpFile = File.fromArray([os.tmpdir(), `eide-binaries${binVersion}.zip`]); /* make dir */ binFolder.CreateDir(true); @@ -354,6 +375,11 @@ async function checkAndInstallBinaries(constex: vscode.ExtensionContext, forceIn }); } + /* del tmp file */ + if (tmpFile.IsFile()) { + try { fs.unlinkSync(tmpFile.path) } catch (error) { } + } + } catch (error) { GlobalEvent.emit('error', error); } @@ -397,7 +423,7 @@ function exportEnvToSysPath() { async function InitComponents(context: vscode.ExtensionContext): Promise { - // init all modules + // init resource manager ResManager.GetInstance(context); /* check binaries, if not found, install it ! */ @@ -460,6 +486,13 @@ async function InitComponents(context: vscode.ExtensionContext): Promise = new Map(); + + resolveCustomTextEditor(document: vscode.TextDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): void | Thenable { + + const viewFile = new File(document.fileName); + const title = viewFile.noSuffixName; + + // view uid + const uid = `${viewFile.path}-${Date.now().toString()}`; + + // init + webviewPanel.title = title; + webviewPanel.iconPath = vscode.Uri.file(ResManager.GetInstance().GetIconByName('Report_16x.svg').path); + webviewPanel.webview.html = this.genHtmlCont(title, 'No Content'); + webviewPanel.onDidDispose(() => this.deleteRef(viewFile.path, uid)); + + if (!viewFile.IsFile()) { + webviewPanel.webview.html = this.genHtmlCont(title, `Error: Not found file '${viewFile.path}' !`); + return; + } + + // check tool type + const conf: { tool?: string, fileName?: string } = yaml.parse(viewFile.Read()); + if (!conf.tool) { + webviewPanel.webview.html = this.genHtmlCont(title, `Error: Invalid toolchain type !`); + return; + } + + let toolName = 'ARM_MICRO'; + let fileDepth = SettingManager.GetInstance().getMapViewParserDepth(); + + if (fileDepth < 0) + fileDepth = 1; + + switch (conf.tool) { + case 'AC5': + case 'AC6': + toolName = 'ARM_MICRO'; + break; + case 'GCC': + toolName = 'GCC_ARM'; + break; + default: + webviewPanel.webview.html = this.genHtmlCont(title, `Error: We not support this toolchain type: '${conf.tool}' !`); + return; + } + + // get map file + const defName = viewFile.noSuffixName + '.map'; + const mapFile = File.fromArray([viewFile.dir, conf.fileName || defName]); + + if (!mapFile.IsFile()) { + webviewPanel.webview.html = this.genHtmlCont(title, `Error: Not found file '${mapFile.path}' !`); + return; + } + + // auto update when file changed + try { + + let mInfo = this.mapViews.get(viewFile.path); + if (mInfo == undefined) { + mInfo = { watcher: new FileWatcher(mapFile, false), refList: [] }; + mInfo.watcher.OnChanged = () => this.showMapView(); + mInfo.watcher.Watch(); + this.mapViews.set(viewFile.path, mInfo); + } + + this.pushRef(viewFile.path, { + uid: uid, + vscWebview: webviewPanel.webview, + title: title, + toolName: toolName, + treeDepth: fileDepth, + mapPath: mapFile.path + }); + + } catch (error) { + // do nothing + } + + this.showMapView(); + } + + private pushRef(viewFilePath: string, item: MapViewRef) { + + const mInfo = this.mapViews.get(viewFilePath); + if (mInfo) { + const idx = mInfo.refList.findIndex((inf) => inf.uid == item.uid); + if (idx == -1) { + mInfo.refList.push(item); + } + } + } + + private deleteRef(viewFilePath: string, uid: string) { + + const mInfo = this.mapViews.get(viewFilePath); + if (mInfo) { + + const idx = mInfo.refList.findIndex((inf) => inf.uid == uid); + if (idx != -1) { + delete mInfo.refList[idx].vscWebview; + mInfo.refList.splice(idx, 1); + } + + if (mInfo.refList.length == 0) { + try { mInfo.watcher.Close(); } catch (error) { } + this.mapViews.delete(viewFilePath); + } + } + } + + private showMapView() { + + this.mapViews.forEach((mInfo) => { + + for (const vInfo of mInfo.refList) { + try { + const execPath = ResManager.GetInstance().getBuilderDir() + File.sep + 'memap'; + const lines = ChildProcess + .execSync(`${execPath} -t ${vInfo.toolName} -d ${vInfo.treeDepth} "${vInfo.mapPath}"`) + .toString().split(/\r\n|\n/); + + // append color + for (let index = 0; index < lines.length; index++) { + const line = lines[index]; + lines[index] = line + .replace(/\((\+[^0]\d*)\)/g, `($1)`) + .replace(/\((\-[^0]\d*)\)/g, `($1)`) + .replace(/^(\s*\|\s*)(Subtotals)/, `$1$2`) + .replace(/^(\s*Total)/, `\n$1`); + } + + vInfo.vscWebview.html = this.genHtmlCont(vInfo.title, lines.join('\n')); + + } catch (error) { + vInfo.vscWebview.html = this.genHtmlCont(vInfo.title, `Parse error: \r\n${error.message}`); + } + } + }); + } + + private genHtmlCont(title: string, cont: string): string { + return ` + + + + + + ${title} + + + +
+
${cont}
+
+ + + `; + } +} + //----------------------------------------- const sfrMap: Map = new Map(); diff --git a/src/utility.ts b/src/utility.ts index 590b04e3..7c9c8b75 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -25,15 +25,16 @@ import * as vscode from 'vscode'; import * as crypto from 'crypto'; import * as child_process from 'child_process'; +import * as fs from 'fs'; import { WorkspaceManager } from "./WorkspaceManager"; import { CmdLineHandler } from "./CmdLineHandler"; -import { GlobalEvent } from "./GlobalEvents"; import { ExceptionToMessage } from "./Message"; import { NetRequest, NetResponse } from '../lib/node-utility/NetRequest'; import { File } from '../lib/node-utility/File'; +import { GitFileInfo } from './WebInterface/GithubInterface'; -export function runShellCommand(title: string, commandLine: string, cmdPath?: string, env?: any) { +export function runShellCommand(title: string, commandLine: string, cmdPath?: string, env?: any): Error | undefined { try { if (WorkspaceManager.getInstance().hasWorkspaces()) { // use task @@ -54,7 +55,7 @@ export function runShellCommand(title: string, commandLine: string, cmdPath?: st terminal.sendText(CmdLineHandler.DeleteCmdPrefix(commandLine)); } } catch (error) { - GlobalEvent.emit('msg', ExceptionToMessage(error, 'Warning')); + return error; } } @@ -110,12 +111,28 @@ export const toolsUrlMap = { }; const hostMap: any = { - 'api.github.com': 'api-github.em-ide.com', - 'raw.githubusercontent.com': 'raw-github.em-ide.com' + 'api.github.com': [ + 'api-github.em-ide.com' + ], + 'raw.githubusercontent.com': [ + 'raw-github.em-ide.com', + 'raw-github.github0null.io' + ] }; export function redirectHost(url: string) { - for (const host in hostMap) { url = url.replace(host, hostMap[host]); } + + // replace host + for (const host in hostMap) { + const hostList = hostMap[host]; + if (hostList.length > 1) { + const idx = Math.floor(Math.random() * hostList.length); // random index + url = url.replace(host, hostList[idx]); + } else { + url = url.replace(host, hostList[0]); + } + } + return url; } @@ -249,3 +266,128 @@ export async function getDownloadUrlFromGit(repo: string, folder: string, fileNa resolve(fInfo); }); } + +export async function readGithubRepoFolder(remoteUrl_: string, token?: vscode.CancellationToken): Promise { + + // URL: https://api.github.com/repos/github0null/eide-doc/contents/eide-template-list + const remoteUrl = remoteUrl_.replace(/^http[s]?:\/\//, ''); + const netReq = new NetRequest(); + + let reqError: Error | undefined; + netReq.on('error', (err) => { + (err).message = `Failed to connect '${remoteUrl}'`; + reqError = err; + }); + + const pathArr = (remoteUrl).split('/'); + const hostName = pathArr[0]; + const path = '/' + pathArr.slice(1).join('/'); + + token?.onCancellationRequested(() => { + netReq.emit('abort'); + }); + + const res = await netReq.Request({ + host: hostName, + path: path, + timeout: 3000, + headers: { 'User-Agent': 'Mozilla/5.0' } + }, 'https'); + + if (!res.success) { + const errMsg = res.msg ? `, msg: ${res.msg}` : ''; + return new Error(`Can't connect to github repository !${errMsg}`); + } else if (res.content === undefined) { + const errMsg = res.msg ? `, msg: ${res.msg}` : ''; + return new Error(`Can't get content from github repository !${errMsg}`); + } + + if (reqError) { + return reqError; + } + + return res.content; +} + +export function genGithubHash(f: File | Buffer): string { + if (f instanceof File) { + const header = Buffer.from('blob ' + f.getSize() + '\0'); + const buf = Buffer.concat([header, fs.readFileSync(f.path)], header.length + f.getSize()); + const hash = crypto.createHash('sha1'); + hash.update(buf); + return hash.digest('hex'); + } else { + const header = Buffer.from('blob ' + f.length + '\0'); + const buf = Buffer.concat([header, f], header.length + f.length); + const hash = crypto.createHash('sha1'); + hash.update(buf); + return hash.digest('hex'); + } +} + +interface FileCacheInfo { + + version: string; + + files: { name: string; sha: string; }[]; +} + +export class FileCache { + + private folder: File; + private cacheFile: File; + private cache: FileCacheInfo; + + public constructor(rootFolder: File) { + this.folder = rootFolder; + this.cacheFile = File.fromArray([rootFolder.path, 'cache.json']); + this.cache = this.cacheFile.IsFile() ? JSON.parse(this.cacheFile.Read()) : { version: '1.0', files: [] }; + } + + public add(name: string, sha: string) { + + const idx = this.cache.files.findIndex((inf) => inf.name == name); + + if (idx != -1) { + this.cache.files[idx].sha = sha; + } else { + this.cache.files.push({ + name: name, + sha: sha + }); + } + } + + public get(name: string, sha: string): File | undefined { + + const idx = this.cache.files.findIndex((inf) => { + return inf.name == name && inf.sha == sha; + }); + + if (idx == -1) { + return undefined; + } + + const f = File.fromArray([this.folder.path, this.cache.files[idx].name]); + if (!f.IsFile()) { + return undefined; + } + + return f; + } + + public clear(name?: string) { + if (name) { + const idx = this.cache.files.findIndex((inf) => inf.name == name); + if (idx != -1) { + this.cache.files.splice(idx, 1); + } + } else { + this.cache.files = []; + } + } + + public save() { + this.cacheFile.Write(JSON.stringify(this.cache)); + } +}