diff --git a/.gitignore b/.gitignore index 0692f98..01e61e7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,10 @@ node_modules .vscode-test/ *.vsix package-lock.json + +# Reference code (not included in build) +ref/ + +# Auto-generated type declaration files +src/vue/auto-imports.d.ts +src/vue/components.d.ts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..dc37698 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,19 @@ +image: node:22 +stages: + - deploy + +deploy-job: # This job runs in the deploy stage. + stage: deploy # It only runs when *both* jobs in the test stage complete successfully. + script: + - npm config set registry https://registry.npmmirror.com + - npm install -g @vscode/vsce + - npm install + - npm run compile + - npm run package:vs + - ls ./ + artifacts: + paths: + - "*.vsix" # 所有 .vsix 文件(如 dist/extension.vsix) + expire_in: 1 week # 产物保留 1 周(可选,默认 30 天) + environment: production + when: manual # 标记为手动作业 diff --git a/.vscodeignore b/.vscodeignore index 0d1fc00..9dca7e3 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,6 +1,7 @@ .vscode/** .vscode-test/** src/** +ref/** .gitignore .yarnrc vsc-extension-quickstart.md @@ -9,7 +10,9 @@ vsc-extension-quickstart.md **/*.map **/*.ts **/.vscode-test.* +# Exclude all node_modules except marked **/node_modules/** +!node_modules/marked/** **/.git/** **/package-lock.json **/.DS_Store diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..664d4d6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# CLAUDE.md + +此文件为 Claude Code (claude.ai/code) 在本仓库中工作时提供指导。 + +## 项目概述 + +这是一个用于 RT-Thread 和 RT-Thread Smart 开发的 VS Code 扩展。该扩展遵循最小化干预 VS Code 的原则,同时允许用户使用 RT-Thread 的脚本功能进行个性化定制。 + +## 常用命令 + +### 开发相关 +```bash +# Install dependencies +npm install + +# Build Vue frontend (required before extension build) +npm run build:vue + +# Compile TypeScript extension +npm run compile + +# Watch mode for development +npm run watch + +# Lint the code +npm run lint + +# Run tests +npm run test + +# Build extension package +npm run vscode:prepublish +``` + +### Vue 前端开发(在 src/vue 工作区中) +```bash +cd src/vue +npm install +npm run dev # Development server +npm run build # Production build +``` + +## 架构说明 + +### 扩展结构 +- **主入口**: `src/extension.ts` - 基于 `rtconfig.h` 或 `.vscode/workspace.json` 的存在来激活 +- **两种运行模式**: + - **项目模式** (`isRTThread`): 包含 `rtconfig.h` 的单个 RT-Thread 项目 + - **工作区模式** (`isRTThreadWorksapce`): 通过 `.vscode/workspace.json` 管理的多个 BSP 项目 + +### 核心组件 +1. **Webviews** (`src/webviews/`): 管理基于 Vue 的 UI 面板 + - 设置、关于、创建项目、项目视图 + - 每个 webview 在 `src/vue/` 中都有对应的 Vue 应用 + +2. **项目管理** (`src/project/`): + - 文件资源管理器的树形提供器 + - 用于视觉指示的文件装饰提供器 + - BSP 项目的命令执行 + +3. **终端集成** (`src/terminal.ts`): + - 管理 RT-Thread 终端会话 + - 执行构建命令和自定义菜单命令 + +4. **虚拟环境** (`src/venv.ts`): + - Windows 特定的 Python 虚拟环境设置 + - 管理 env 脚本安装 + +### 配置文件 +- **`.vscode/workspace.json`**: 多 BSP 工作区配置 +- **`.vscode/project.json`**: 单个项目文件结构(由 scons 生成) +- **`~/.env/cfg.json`**: RT-Thread 源码路径配置 +- **`~/.env/tools/scripts/sdk_cfg.json`**: 工具链配置 + +### 构建系统 +- TypeScript 编译为 CommonJS 用于 VS Code 扩展 +- Vue 3 + Element Plus 前端使用 Vite 构建 +- 多页面 Vue 应用构建到 `out/` 目录 +- 通过 `smart.parallelBuidNumber` 设置支持并行构建 + +### 关键设置 +- `smart.menuCommands`: 自定义终端命令数组 +- `smart.parallelBuidNumber`: 并行构建的 CPU 核心数 + +## 重要说明 + +1. 扩展仅在 RT-Thread 项目中激活(包含 `rtconfig.h` 或工作区配置) +2. Windows 系统在首次运行时需要设置 Python 虚拟环境 +3. 必须先构建 Vue 前端再编译扩展 +4. 扩展集成了 Python 扩展(`ms-python.python`) +5. 文件装饰器在工作区模式下标记当前活动的 BSP \ No newline at end of file diff --git a/README_settings.md b/README_settings.md new file mode 100644 index 0000000..a776cf7 --- /dev/null +++ b/README_settings.md @@ -0,0 +1,291 @@ +# RT-Thread Smart Extension 配置文件说明 + +本文档详细说明了 RT-Thread Smart Extension 中使用的各种 JSON 配置文件的结构和字段含义。 + +## 目录 + +1. [工作区相关配置文件](#工作区相关配置文件) +2. [环境配置文件](#环境配置文件) +3. [资源配置文件](#资源配置文件) + +--- + +## 工作区相关配置文件 + +这些配置文件位于项目工作区的 `.vscode` 目录下,用于管理项目结构和工作区设置。 + +### 1. project.json + +**文件路径**: `{workspace}/.vscode/project.json` + +**用途**: 存储单个 RT-Thread 项目的结构信息,包括分组和文件组织。 + +**结构说明**: + +```json +{ + "RT-Thread": "项目根目录路径", + "Groups": [ + { + "name": "分组名称", + "files": [ + "文件路径1", + "文件路径2" + ] + } + ] +} +``` + +**字段详解**: + +- **RT-Thread** (string): RT-Thread 项目的根目录路径 + - 用于指定项目的主要源码目录 + - 可以是绝对路径或相对于工作区的相对路径 + +- **Groups** (array): 项目文件分组数组 + - **name** (string): 分组名称,在项目树中显示 + - **files** (array): 该分组包含的文件路径列表 + - 支持相对路径(相对于工作区根目录) + - 支持以 `..` 开头的相对路径 + - 文件路径将自动解析为绝对路径 + +**使用场景**: +- 由 scons 构建工具生成,用于在 VS Code 中显示项目文件结构 +- 支持文件分组管理,方便代码组织和浏览 +- 在项目树视图中按分组显示源文件 + +### 2. workspace.json + +**文件路径**: `{workspace}/.vscode/workspace.json` + +**用途**: 管理多个 BSP(Board Support Package)项目的工作区配置。 + +**结构说明**: + +```json +{ + "currentProject": "当前激活的项目路径", + "bsps": { + "folder": "BSP目录相对路径", + "stars": [ + "bsp项目路径1", + "bsp项目路径2" + ] + } +} +``` + +**字段详解**: + +- **currentProject** (string): 当前激活的 BSP 项目路径 + - 指定当前工作的 BSP 项目 + - 用于在多个 BSP 项目之间切换 + - 路径相对于 bsps.folder 指定的目录 + +- **bsps** (object): BSP 项目配置 + - **folder** (string): BSP 项目的根目录,相对于工作区根目录 + - **stars** (array): 收藏的/可用的 BSP 项目列表 + - 每个项目路径相对于 `bsps.folder` + - 支持嵌套目录结构(如 `stm32/stm32f407-rt-spark`) + +**使用场景**: +- 工作区模式下管理多个 BSP 项目 +- 在项目树中显示可用的 BSP 项目列表 +- 支持项目切换和状态保存 + +### 3. rtthread.json + +**文件路径**: `{workspace}/.vscode/rtthread.json` + +**用途**: 存储 RT-Thread BSP 项目的板级信息和配置。 + +**结构说明**: + +```json +{ + "board_info": { + "name": "开发板名称", + "description": "开发板描述", + "manufacturer": "制造商", + "board": "板型标识", + "path": "BSP路径" + } +} +``` + +**字段详解**: + +- **board_info** (object): 开发板信息 + - **name** (string): 开发板的显示名称 + - **description** (string): 开发板的详细描述 + - **manufacturer** (string): 开发板制造商 + - **board** (string): 开发板的标识符,通常与目录名对应 + - **path** (string): BSP 在 RT-Thread 源码中的相对路径 + +**使用场景**: +- 在创建项目界面显示开发板信息 +- 为特定 BSP 项目提供元数据 +- 与 `resources/bi.json` 中的板级信息对应 + +--- + +## 环境配置文件 + +这些配置文件位于用户主目录的 `.env` 文件夹下,用于管理 RT-Thread 开发环境。 + +### 1. cfg.json + +**文件路径**: `~/.env/cfg.json` + +**用途**: 存储 RT-Thread 主干路径配置。 + +**结构说明**: + +```json +[ + { + "name": "配置项名称", + "path": "RT-Thread主干路径", + "description": "配置项描述" + } +] +``` + +**字段详解**: + +- **name** (string): 配置项的名称,通常为 "RT-Thread" +- **path** (string): RT-Thread 主干代码的本地路径 + - 指向 RT-Thread 源码仓库的根目录 + - 用于访问 BSP 和系统源码 +- **description** (string): 配置项的描述信息 + +**使用场景**: +- 在设置界面配置 RT-Thread 源码路径 +- 创建项目时定位 BSP 目录 +- 提供系统源码访问路径 + +### 2. sdk_cfg.json + +**文件路径**: `~/.env/tools/scripts/sdk_cfg.json` + +**用途**: 存储工具链(SDK)配置信息。 + +**结构说明**: + +```json +[ + { + "name": "工具链名称", + "path": "工具链路径", + "description": "工具链描述" + } +] +``` + +**字段详解**: + +- **name** (string): 工具链的名称 + - 例如: "arm-none-eabi-gcc", "armcc", "iar" 等 +- **path** (string): 工具链的安装路径 + - 指向编译器的 bin 目录或根目录 + - 用于构建时定位编译工具 +- **description** (string): 工具链的描述信息 + - 例如: "ARM GNU GCC", "Keil MDK-ARM" 等 + +**使用场景**: +- 在设置界面管理多个工具链 +- 为不同项目选择合适的编译工具链 +- 自动配置构建环境 + +### 3. env.json + +**文件路径**: `~/.env/tools/scripts/env.json` + +**用途**: 存储环境脚本的版本信息。这个文件在env脚本git仓库中就默认携带。 + +**结构说明**: + +```json +{ + "version": "环境脚本版本号" +} +``` + +**字段详解**: + +- **version** (string): 环境脚本的版本号 + - 格式通常为语义化版本,如 "v2.0.1" + - 用于显示和检查环境脚本更新 + +**使用场景**: +- 在设置界面显示当前环境版本 +- 检查环境更新状态 +- 版本兼容性检查 + +--- + +## 资源配置文件 + +### 1. bi.json (Board Information) + +**文件路径**: `resources/bi.json` + +**用途**: 创建工程时支持的开发板信息数据库。 + +**结构说明**: + +```json +[ + { + "manufacturer": "制造商名称", + "boards": [ + { + "name": "开发板名称", + "description": "开发板描述", + "board": "板型标识", + "path": "BSP相对路径" + } + ] + } +] +``` + +**字段详解**: + +- **manufacturer** (string): 开发板制造商名称 + - 例如: "ST", "QEMU", "Raspberry Pi" 等 + - 用于在创建项目界面按制造商分类 + +- **boards** (array): 该制造商的开发板列表 + - **name** (string): 开发板的显示名称 + - **description** (string): 开发板的详细描述和特性 + - **board** (string): 开发板的唯一标识符 + - **path** (string): 在 RT-Thread 源码中的 BSP 相对路径 + +**使用场景**: +- 在创建项目向导中显示可用开发板 +- 按制造商分类显示开发板选项 +- 提供开发板详细信息和 BSP 路径映射 + +--- + +## 配置文件生命周期 + +1. **安装阶段**: 创建 `~/.env/` 目录和基础配置文件 +2. **配置阶段**: 用户通过设置界面配置 `cfg.json` 和 `sdk_cfg.json` +3. **项目创建**: 生成 `.vscode/rtthread.json` 和相关配置 +4. **构建阶段**: scons 生成 `.vscode/project.json` 或 `.vscode/workspace.json` +5. **运行阶段**: 扩展读取所有配置文件以提供功能 + +## 注意事项 + +1. **路径处理**: 所有相对路径都会被解析为绝对路径 +2. **文件权限**: 确保配置文件具有适当的读写权限 +3. **版本兼容**: 不同版本的扩展可能使用不同的配置格式 +4. **备份建议**: 重要配置文件建议定期备份 +5. **错误处理**: 配置文件损坏时,扩展会使用默认配置或显示错误提示 + +--- + +*此文档基于 RT-Thread Smart Extension v0.4.12 版本编写* diff --git a/eslint.config.mjs b/eslint.config.mjs index d5c0b53..83da18e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,6 +3,7 @@ import tsParser from "@typescript-eslint/parser"; export default [{ files: ["**/*.ts"], + ignores: ["ref/**"], }, { plugins: { "@typescript-eslint": typescriptEslint, diff --git a/package-lock.json b/package-lock.json index 62c5468..8598c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "rt-thread-smart", - "version": "0.4.11", + "version": "0.4.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "rt-thread-smart", - "version": "0.4.11", + "version": "0.4.12", "workspaces": [ "src/vue" ], @@ -1862,6 +1862,21 @@ } } }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmmirror.com/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", @@ -5944,7 +5959,11 @@ }, "src/vue": { "name": "smart-vue", - "version": "0.0.1" + "version": "0.0.1", + "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0" + } } } } diff --git a/package.json b/package.json index c04bf47..6677309 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "publisher": "rt-thread", "displayName": "RT-Thread Smart Extension", "description": "Smart Helper Extension for RT-Thread Development", - "version": "0.4.11", + "version": "0.4.12", "repository": { "url": "https://github.com/RT-Thread/vscode-rt-smart" }, @@ -56,8 +56,12 @@ }, "commands": [ { - "command": "extension.showHome", - "title": "Show RT-Thread Home" + "command": "extension.showSetting", + "title": "Show RT-Thread Setting" + }, + { + "command": "extension.showCreateProject", + "title": "Create RT-Thread Project" }, { "command": "extension.buildProject", @@ -172,6 +176,7 @@ ] }, "scripts": { + "package:vs": "vsce package --no-dependencies", "vscode:prepublish": "npm run compile", "build:vue": "npm run build --workspace=smart-vue", "compile": "npm run build:vue && tsc -p ./", diff --git a/src/dock.ts b/src/dock.ts index 6a65fcb..49078a8 100644 --- a/src/dock.ts +++ b/src/dock.ts @@ -19,30 +19,60 @@ class CmdTreeDataProvider implements vscode.TreeDataProvider { } if (isRTThreadProject() != true && isRTThreadWorksapce() != true) { - // only show Home command - let home = new vscode.TreeItem("Home", vscode.TreeItemCollapsibleState.None); - home.iconPath = new vscode.ThemeIcon("home"); - home.label = "Home"; - home.command = { - command: "extension.showHome", - title: "show home page", + // only show Create Project and RT-Thread Setting command when not in RT-Thread project + let createProject = new vscode.TreeItem("create project", vscode.TreeItemCollapsibleState.None); + createProject.iconPath = new vscode.ThemeIcon("new-folder"); + createProject.label = "create project"; + createProject.command = { + command: "extension.showCreateProject", + title: "show create project page", arguments: [], }; - return [home]; + let rtSetting = new vscode.TreeItem("rt-thread setting", vscode.TreeItemCollapsibleState.None); + rtSetting.iconPath = new vscode.ThemeIcon("settings-gear"); + rtSetting.label = "rt-thread setting"; + rtSetting.command = { + command: "extension.showSetting", + title: "show rt-thread setting page", + arguments: [], + }; + + let about = new vscode.TreeItem("About", vscode.TreeItemCollapsibleState.None); + about.iconPath = new vscode.ThemeIcon("info"); + about.label = "About"; + about.command = { + command: "extension.showAbout", + title: "show about page", + arguments: [], + } + + return [createProject, rtSetting, about]; } if (!element) { let children = []; - let home = new vscode.TreeItem("Home", vscode.TreeItemCollapsibleState.None); - home.iconPath = new vscode.ThemeIcon("home"); - home.label = "Home"; - home.command = { - command: "extension.showHome", - title: "show home page", + + // 添加创建工程项 + let createProject = new vscode.TreeItem("create project", vscode.TreeItemCollapsibleState.None); + createProject.iconPath = new vscode.ThemeIcon("new-folder"); + createProject.label = "create project"; + createProject.command = { + command: "extension.showCreateProject", + title: "show create project page", + arguments: [], + }; + children.push(createProject); + + let rtSetting = new vscode.TreeItem("rt-thread setting", vscode.TreeItemCollapsibleState.None); + rtSetting.iconPath = new vscode.ThemeIcon("settings-gear"); + rtSetting.label = "rt-thread setting"; + rtSetting.command = { + command: "extension.showSetting", + title: "show rt-thread setting page", arguments: [], }; - children.push(home); + children.push(rtSetting); for (const [key, value] of Object.entries(cmds)) { let item = new vscode.TreeItem(value.name, value.isExpanded? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed); diff --git a/src/extension.ts b/src/extension.ts index e998622..26b472e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,8 +4,9 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; -import { openHomeWebview } from './webviews/home'; +import { openSettingWebview } from './webviews/setting'; import { openAboutWebview } from './webviews/about'; +import { openCreateProjectWebview } from './webviews/create-project'; import { initOnDidChangeListener } from './listener'; import { executeCommand, initTerminal } from './terminal'; import { getMenuItems, getParallelBuildNumber } from './smart'; @@ -16,9 +17,19 @@ import { openWorkspaceProjectsWebview } from './webviews/project'; import { initProjectTree } from './project/tree'; import { DecorationProvider } from './project/fileDecorationProvider'; import { getCurrentProjectInWorkspace } from './webviews/project'; +import { initCurrentProject } from './project/cmd'; let _context: vscode.ExtensionContext; +export function postMessageExtensionData(context: vscode.ExtensionContext, panel: vscode.WebviewPanel) { + // 获取插件版本号(从 package.json 中读取) + const extensionVersion = context.extension.packageJSON.version; + const extensionNaeme = context.extension.packageJSON.name; + // 例如:version 为 "1.0.0" + console.log('插件版本号:', extensionVersion); + panel.webview.postMessage({ version: extensionVersion, name: extensionNaeme }); +} + // 有两种模式 // isRTThreadWorksapce - workspace模式,会定位.vscode/workspace.json文件是否存在,是否启用 // isRTThread - 项目模式,rtconfig.h文件是否存在 @@ -29,6 +40,11 @@ export async function activate(context: vscode.ExtensionContext) { _context = context; + // 获取插件版本号(从 package.json 中读取) + const extensionVersion = context.extension.packageJSON.version; + // 例如:version 为 "1.0.0" + console.log('插件版本号:', extensionVersion); + // init context for isRTThread, isRTThreadWorksapce vscode.commands.executeCommand('setContext', 'isRTThread', isRTThread); context.workspaceState.update('isRTThread', isRTThread); @@ -56,6 +72,8 @@ export async function activate(context: vscode.ExtensionContext) { let currentProject = getCurrentProjectInWorkspace(); if (currentProject) { DecorationProvider.getInstance().markFile(vscode.Uri.file(currentProject)); + // 初始化当前项目到 cmd.ts 的 _currentProject 变量 + initCurrentProject(currentProject); } } } @@ -81,16 +99,11 @@ export async function activate(context: vscode.ExtensionContext) { initOnDidChangeListener(context); // register commands - vscode.commands.registerCommand('extension.showAbout', () => { - openAboutWebview(context); - }); vscode.commands.registerCommand('extension.executeCommand', (arg1, arg2) => { - if (arg1) - { + if (arg1) { executeCommand(arg1); } - if (arg2) - { + if (arg2) { executeCommand(arg2); } }); @@ -99,13 +112,20 @@ export async function activate(context: vscode.ExtensionContext) { // open file vscode.commands.executeCommand('vscode.open', vscode.Uri.file(arg.fn)); } - }) + }); } } - vscode.commands.registerCommand('extension.showHome', () => { - openHomeWebview(context); + vscode.commands.registerCommand('extension.showSetting', () => { + openSettingWebview(context); + }); + vscode.commands.registerCommand('extension.showCreateProject', () => { + openCreateProjectWebview(context); }); + vscode.commands.registerCommand('extension.showAbout', () => { + openAboutWebview(context); + }); + if (isRTThreadWorksapce) { vscode.commands.registerCommand('extension.showWorkspaceSettings', () => { openWorkspaceProjectsWebview(context); @@ -115,11 +135,11 @@ export async function activate(context: vscode.ExtensionContext) { /* initialize dock view always */ initDockView(context); - initExperimentStatusBarItem(context) + initExperimentStatusBarItem(context); } function initExperimentStatusBarItem(context: vscode.ExtensionContext) { - if (false){ + if (false) { const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 5); statusItem.text = '$(beaker) 实验性功能'; statusItem.tooltip = 'Experimental features'; diff --git a/src/project/cmd.ts b/src/project/cmd.ts index cc73f1b..fef71c0 100644 --- a/src/project/cmd.ts +++ b/src/project/cmd.ts @@ -8,6 +8,12 @@ import { readWorkspaceJson, writeWorkspaceJson } from '../webviews/project'; let _currentProject: string = ''; +export function initCurrentProject(projectPath: string) { + if (projectPath) { + _currentProject = projectPath; + } +} + export function fastBuildProject(arg: any) { if (arg) { const cpus = os.cpus().length; diff --git a/src/vue/about/App.vue b/src/vue/about/App.vue index 0b37b5f..61141d4 100644 --- a/src/vue/about/App.vue +++ b/src/vue/about/App.vue @@ -1,10 +1,12 @@ @@ -12,6 +14,8 @@ import { onMounted, ref } from 'vue'; import { imgUrl } from '../assets/img'; import { sendCommand } from '../api/vscode'; +import { extensionInfo } from '../setting/data'; +import Banner from '../components/Banner.vue'; let readmeMarkdown = ref(''); @@ -38,6 +42,51 @@ onMounted(() => { diff --git a/src/vue/assets/img.ts b/src/vue/assets/img.ts index 3acd7e9..8b0e7cf 100644 --- a/src/vue/assets/img.ts +++ b/src/vue/assets/img.ts @@ -2,4 +2,4 @@ export const imgUrl = { "head-logo": 'data:image/png;base64,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' -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/vue/assets/rt-thread-logo.png b/src/vue/assets/rt-thread-logo.png new file mode 100644 index 0000000..e7df0a2 Binary files /dev/null and b/src/vue/assets/rt-thread-logo.png differ diff --git a/src/vue/auto-imports.d.ts b/src/vue/auto-imports.d.ts deleted file mode 100644 index 9d24007..0000000 --- a/src/vue/auto-imports.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// noinspection JSUnusedGlobalSymbols -// Generated by unplugin-auto-import -// biome-ignore lint: disable -export {} -declare global { - -} diff --git a/src/vue/components.d.ts b/src/vue/components.d.ts deleted file mode 100644 index 1851f37..0000000 --- a/src/vue/components.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable */ -// @ts-nocheck -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 -// biome-ignore lint: disable -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - ElAside: typeof import('element-plus/es')['ElAside'] - ElButton: typeof import('element-plus/es')['ElButton'] - ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElContainer: typeof import('element-plus/es')['ElContainer'] - ElDialog: typeof import('element-plus/es')['ElDialog'] - ElForm: typeof import('element-plus/es')['ElForm'] - ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElHeader: typeof import('element-plus/es')['ElHeader'] - ElInput: typeof import('element-plus/es')['ElInput'] - ElMain: typeof import('element-plus/es')['ElMain'] - ElMenu: typeof import('element-plus/es')['ElMenu'] - ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] - ElOption: typeof import('element-plus/es')['ElOption'] - ElProgress: typeof import('element-plus/es')['ElProgress'] - ElSelect: typeof import('element-plus/es')['ElSelect'] - ElTable: typeof import('element-plus/es')['ElTable'] - ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - } - export interface GlobalDirectives { - vLoading: typeof import('element-plus/es')['ElLoadingDirective'] - } -} diff --git a/src/vue/components/Banner.vue b/src/vue/components/Banner.vue new file mode 100644 index 0000000..08b8cda --- /dev/null +++ b/src/vue/components/Banner.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/src/vue/create-project/App.vue b/src/vue/create-project/App.vue new file mode 100644 index 0000000..a9c2feb --- /dev/null +++ b/src/vue/create-project/App.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/src/vue/create-project/data.ts b/src/vue/create-project/data.ts new file mode 100644 index 0000000..9473c2c --- /dev/null +++ b/src/vue/create-project/data.ts @@ -0,0 +1,39 @@ +import { ref } from 'vue'; + +// data model for create project +export let extensionInfo = ref({ + envRoot: "~/.env", + version: "0.4.11" +}); + +export let projectInfo = ref({ + folder: "", + name: "", + board: "", + manufacturer: "", + projectList: [ + { + manufacturer: "stm32", + boards: [ + "stm32f407-rt-spark" + ], + }, + { + manufacturer: "qemu", + boards: [ + "qemu-vexpress-a9" + ] + } + ], + linkRTT: false, + linkDriver: false, + readme: "README.md" +}); + +export let envInfo = ref({ + version: "0.0.1", + path: "~/.env", + rtConfig: { + path: "" + } +}); diff --git a/src/vue/create-project/index.html b/src/vue/create-project/index.html new file mode 100644 index 0000000..aac0879 --- /dev/null +++ b/src/vue/create-project/index.html @@ -0,0 +1,12 @@ + + + + + + RT-Thread Create Project + + +
+ + + diff --git a/src/vue/home/main.ts b/src/vue/create-project/main.ts similarity index 69% rename from src/vue/home/main.ts rename to src/vue/create-project/main.ts index efe493a..43d27f6 100644 --- a/src/vue/home/main.ts +++ b/src/vue/create-project/main.ts @@ -1,7 +1,5 @@ import { createApp } from 'vue' import App from './App.vue' -import router from './router' const app = createApp(App) -app.use(router) app.mount('#app') diff --git a/src/vue/env.d.ts b/src/vue/env.d.ts new file mode 100644 index 0000000..323c78a --- /dev/null +++ b/src/vue/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/src/vue/home/data.ts b/src/vue/home/data.ts deleted file mode 100644 index d888551..0000000 --- a/src/vue/home/data.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { ref } from 'vue'; - -// data model -export let extensionInfo = ref({ - envRoot: "~/.env", - version: "0.4.11" -}); - -export let projectInfo = ref({ - folder: "", - name: "", - board: "", - manufacturer: "", - projectList: [ - { - manufacturer: "stm32", - boards: [ - "stm32f407-rt-spark" - ], - }, - { - manufacturer: "qemu", - boards: [ - "qemu-vexpress-a9" - ] - } - ], - linkRTT: false, - linkDriver: false, - readme: "README.md" -}); - -export const analysisInfo = ref({ - mapFile: "rt-thread.map", - mapFileUrl: "", - analysisTableList: [ - { - title: "全部符号", - children: [ - { - name: "name01", - path: "0x00", - size: "12", - }, - { - name: "name02", - path: "0x20", - size: "16", - }, - ] - }, - { - title: "按文件", - children: [ - { - name: "file01", - path: "0x00", - size: "12", - }, - { - name: "file02", - path: "0x20", - size: "16", - }, - ] - }, - { - title: "按Groups", - children: [ - { - name: "Groups01", - path: "0x00", - size: "12", - }, - { - name: "Groups02", - path: "0x20", - size: "16", - }, - ] - } - ], - analysisTitleList: [ - { - title: "名称", - field: "name", - }, - { - title: "地址", - field: "path", - }, - { - title: "大小", - field: "size", - }, - ] -}); - -export const envInfo = ref({ - progressNum: 0, - selectRow: null, - - version : "v2.0.1", - path: "~/.env", - - environmentTitleList: [ - { - title: "名称", - field: "name", - }, - { - title: "路径", - field: "path", - }, - { - title: "描述", - field: "description", - }, - ], - environmentData: [ - { - name: "gcc", - path: "d:/tools/toolchains/arm-none-eabi-gcc", - description: "ARM GNU GCC", - } - ], - dialogVisible: false, - editMode: false, - addToolchain: { - name: "", - path: "", - description: "" - }, - radioChange: 0, -}); - -export const configInfo = ref({ - configTitleList: [ - { - title: "名称", - field: "name", - }, - { - title: "路径", - field: "path", - }, - { - title: "描述", - field: "description", - }, - ], - configData: [ - { - name: "RT-Thread", - path: "d:/workspace/rt-thread", - description: "RT-Thread 主干版本", - } - ], - dialogVisible: false, - editConfigItem: { - name: "", - path: "", - description: "" - } -}); diff --git a/src/vue/home/router.ts b/src/vue/home/router.ts deleted file mode 100644 index 882f684..0000000 --- a/src/vue/home/router.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { createRouter, createWebHashHistory } from "vue-router"; -import CreatProject from './view/create-project/index.vue' -import Environment from './view/environment/index.vue' -import SymbolicAnalysis from './view/symbolic-analysis/index.vue' -import Config from './view/config/index.vue' -import LayoutView from './view/layout/index.vue' - -export const routes = [ - { - path: '/', - component: LayoutView, - meta: { - title: '创建工程' - }, - redirect: '/', - children: [ - { - path: '/', - component: CreatProject, - meta: { - title: '创建工程' - } - }, - ] - }, - { - path: '/environment', - component: LayoutView, - meta: { - title: '环境工具' - }, - redirect: '/environment', - children: [ - { - path: '/environment', - component: Environment, - meta: { - title: '环境工具' - }, - }, - ] - }, - { - path: '/symbolic-analysis', - component: LayoutView, - meta: { - title: '符号分析', - hidden: true - }, - redirect: '/symbolic-analysis', - children: [ - { - path: '/symbolic-analysis', - component: SymbolicAnalysis, - meta: { - title: '符号分析', - hidden: true - }, - }, - ] - }, - { - path: '/config', - component: LayoutView, - meta: { - title: '配置' - }, - redirect: '/config', - children: [ - { - path: '/config', - component: Config, - meta: { - title: '配置' - }, - }, - ] - } -] -const router = createRouter({ - history: createWebHashHistory(), - routes -}) - -export default router diff --git a/src/vue/home/view/config/index.less b/src/vue/home/view/config/index.less deleted file mode 100644 index be88af4..0000000 --- a/src/vue/home/view/config/index.less +++ /dev/null @@ -1,74 +0,0 @@ -.content { - width: 100%; - height: 100%; - - .body-box { - width: 80%; - min-width: 800px; - - .title { - font-size: 14px; - color: #333; - margin-bottom: 22px; - } - - :deep(.el-table--fit) { - border: none; - - .el-table__inner-wrapper:before { - width: 0; - } - } - - .table-box { - width: 100%; - display: flex; - column-gap: 20px; - :deep(.el-table tr) { - font-size: 14px; - color: #333; - height: 48px; - } - } - - .el-button { - width: 98px; - height: 40px; - margin: 0; - } - } - - .form-box { - width: 100%; - margin-bottom: 30px; - padding: 24px 24px 0 0; - box-sizing: border-box; - .file-box { - width: 0; - } - .row-box { - width: 100%; - display: flex; - align-items: center; - column-gap: 14px; - - p { - width: 109px; - } - } - - .el-input { - height: 40px; - } - - :deep(.el-form-item__label) { - justify-content: flex-start; - } - } - - .dialog-footer { - display: flex; - justify-content: flex-end; - column-gap: 14px; - } -} \ No newline at end of file diff --git a/src/vue/home/view/config/index.vue b/src/vue/home/view/config/index.vue deleted file mode 100644 index 97a85c0..0000000 --- a/src/vue/home/view/config/index.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - diff --git a/src/vue/home/view/create-project/index.less b/src/vue/home/view/create-project/index.less deleted file mode 100644 index c6f9d41..0000000 --- a/src/vue/home/view/create-project/index.less +++ /dev/null @@ -1,51 +0,0 @@ -.content { - width: 100%; - height: 100%; - - .body-box { - width: 80%; - min-width: 800px; - - .el-form { - :deep(.el-form-item__label) { - justify-content: flex-start; - } - .file-box { - width: 0; - } - - .el-input { - height: 40px; - } - - .el-form-item { - display: flex; - align-items: center; - } - - .row-box { - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - column-gap: 20px; - } - - :deep(.el-checkbox) { - margin-right: 0; - } - - } - - .iframe-box { - width: 100%; - height:auto; - } - } - - .el-button { - width: 98px; - height: 40px; - margin: 0; - } -} \ No newline at end of file diff --git a/src/vue/home/view/create-project/index.vue b/src/vue/home/view/create-project/index.vue deleted file mode 100644 index ee1261b..0000000 --- a/src/vue/home/view/create-project/index.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - diff --git a/src/vue/home/view/environment/index.less b/src/vue/home/view/environment/index.less deleted file mode 100644 index 340133d..0000000 --- a/src/vue/home/view/environment/index.less +++ /dev/null @@ -1,128 +0,0 @@ -.content { - width: 100%; - height: 100%; - - .body-box { - width: 80%; - min-width: 800px; - - .info-box { - display: flex; - justify-content: space-between; - font-size: 14px; - color: #333; - margin-bottom: 28px; - - .info-left { - width: 80%; - padding-top: 10px; - - .info-content { - display: flex; - margin-bottom: 6px; - - p { - width: 84px; - } - - .info-text { - display: flex; - flex-direction: column; - row-gap: 4px; - list-style: none; - } - } - - .progress-box { - width: 100%; - display: flex; - flex-direction: column; - - :deep(.el-progress__text) { - color: #333; - } - } - } - } - } - - .bottom-box { - width: 100%; - - .title { - font-size: 14px; - color: #333; - margin-bottom: 22px; - } - - :deep(.el-table--fit) { - border: none; - - .el-table__inner-wrapper:before { - width: 0; - } - } - - .table-box { - width: 100%; - display: flex; - column-gap: 20px; - .el-radio-group { - width: 100%; - } - :deep(.el-table tr) { - font-size: 14px; - color: #333; - height: 48px; - } - - .btn-box { - width: 98px; - display: flex; - flex-direction: column; - row-gap: 25px; - margin-top: 40px; - } - } - } - - .el-button { - width: 98px; - height: 40px; - margin: 0; - } - - .form-box { - width: 100%; - margin-bottom: 30px; - padding: 24px 24px 0 0; - box-sizing: border-box; - .file-box { - width: 0; - } - .row-box { - width: 100%; - display: flex; - align-items: center; - column-gap: 14px; - - p { - width: 109px; - } - } - - .el-input { - height: 40px; - } - - :deep(.el-form-item__label) { - justify-content: flex-start; - } - } - - .dialog-footer { - display: flex; - justify-content: flex-end; - column-gap: 14px; - } -} \ No newline at end of file diff --git a/src/vue/home/view/environment/index.vue b/src/vue/home/view/environment/index.vue deleted file mode 100644 index 5758e11..0000000 --- a/src/vue/home/view/environment/index.vue +++ /dev/null @@ -1,162 +0,0 @@ - - - - diff --git a/src/vue/home/view/symbolic-analysis/index.less b/src/vue/home/view/symbolic-analysis/index.less deleted file mode 100644 index 650862d..0000000 --- a/src/vue/home/view/symbolic-analysis/index.less +++ /dev/null @@ -1,77 +0,0 @@ -.content { - width: 100%; - height: 100%; - - .body-box { - width: 80%; - min-width: 800px; - - .el-form { - margin-bottom: 22px; - .file-box { - width: 0; - } - .el-form-item { - display: flex; - align-items: center; - - .row-box { - width: 100%; - display: flex; - flex-direction: row; - align-items: center; - column-gap: 20px; - } - - :deep(.el-form-item__label) { - justify-content: flex-start; - } - } - } - - .el-input { - height: 40px; - } - - .el-button { - width: 98px; - height: 40px; - margin: 0; - } - - ul { - list-style: none; - display: flex; - width: 100%; - align-items: center; - - li { - width: 160px; - height: 40px; - border: 1px solid #dcdfe6; - border-bottom: none; - text-align: center; - line-height: 40px; - cursor: pointer; - color: #333; - font-size: 14px; - - &:hover { - color: #1989FA; - } - } - - .active { - color: #1989FA; - } - } - - :deep(.el-table tr) { - height: 48px; - } - - .el-table--fit { - border-top: 1px solid #DCDFE6; - } - } -} \ No newline at end of file diff --git a/src/vue/home/view/symbolic-analysis/index.vue b/src/vue/home/view/symbolic-analysis/index.vue deleted file mode 100644 index 131b4cd..0000000 --- a/src/vue/home/view/symbolic-analysis/index.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - diff --git a/src/vue/package.json b/src/vue/package.json index d369424..59c0e1d 100644 --- a/src/vue/package.json +++ b/src/vue/package.json @@ -7,5 +7,9 @@ "dev": "vite", "build": "vue-tsc && vite build", "preview": "vite preview" + }, + "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0" } } diff --git a/src/vue/package.json.backup b/src/vue/package.json.backup deleted file mode 100644 index 0e2ab5c..0000000 --- a/src/vue/package.json.backup +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "smart-vue", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vue-tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - "@types/vscode": "^1.96.0", - "@vue/shared": "^3.x.x", - "element-plus": "^2.4.2", - "vue": "^3.3.8", - "vue-router": "^4.5.0" - }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.2.1", - "less": "^4.2.2", - "terser": "^5.43.1", - "typescript": "^5.2.2", - "vite": "^6.0.11", - "vue-tsc": "^2.2.0" - } -} diff --git a/src/vue/projects/App.vue b/src/vue/projects/App.vue index 8af1ac2..7b37987 100644 --- a/src/vue/projects/App.vue +++ b/src/vue/projects/App.vue @@ -1,28 +1,25 @@ @@ -31,7 +28,8 @@ import { onMounted, ref, nextTick } from 'vue'; import type { ElTable } from 'element-plus'; import { imgUrl } from '../assets/img'; import { sendCommand } from '../api/vscode'; -import { extensionInfo } from '../home/data'; +import { extensionInfo } from '../setting/data'; +import Banner from '../components/Banner.vue'; const loading = ref(false); // 是否加载中 @@ -54,7 +52,7 @@ const saveBSPProjects = () => { if (tableRef.value) { const selectedRows = (tableRef.value as any).getSelectionRows(); if (selectedRows.length > 0) { - args = selectedRows.map(row => row.path); + args = selectedRows.map((row: any) => row.path); } } @@ -95,7 +93,13 @@ onMounted(() => { diff --git a/src/vue/home/App.vue b/src/vue/setting/App.vue similarity index 60% rename from src/vue/home/App.vue rename to src/vue/setting/App.vue index a1ff173..1604c71 100644 --- a/src/vue/home/App.vue +++ b/src/vue/setting/App.vue @@ -5,8 +5,8 @@ + diff --git a/src/vue/home/view/layout/index.less b/src/vue/setting/view/layout/index.less similarity index 100% rename from src/vue/home/view/layout/index.less rename to src/vue/setting/view/layout/index.less diff --git a/src/vue/home/view/layout/index.vue b/src/vue/setting/view/layout/index.vue similarity index 65% rename from src/vue/home/view/layout/index.vue rename to src/vue/setting/view/layout/index.vue index bacd18e..e1bd266 100644 --- a/src/vue/home/view/layout/index.vue +++ b/src/vue/setting/view/layout/index.vue @@ -1,21 +1,13 @@ + + \ No newline at end of file diff --git a/src/vue/setting/view/toolchains/index.less b/src/vue/setting/view/toolchains/index.less new file mode 100644 index 0000000..ecb5d73 --- /dev/null +++ b/src/vue/setting/view/toolchains/index.less @@ -0,0 +1,180 @@ +/* 工具链管理页面样式 */ +.content { + padding: 20px; + height: 100%; + box-sizing: border-box; + background-color: #f5f7fa; +} + +.body-box { + background-color: #fff; + border-radius: 8px; + padding: 24px; + height: 100%; + display: flex; + flex-direction: column; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +/* 页面头部 */ +.header-section { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #e4e7ed; +} + +.page-title { + font-size: 20px; + font-weight: 600; + color: #303133; + margin: 0; +} + +.header-actions { + display: flex; + gap: 12px; +} + +/* 表格容器 */ +.table-container { + flex: 1; + overflow: auto; + margin-bottom: 20px; +} + +/* 自定义表格样式 */ +.el-table { + font-size: 14px; + + .el-table__header { + th { + background-color: #f5f7fa; + color: #606266; + font-weight: 600; + } + } + + .el-table__row { + &:hover { + background-color: #f5f7fa; + } + } +} + +/* 操作按钮样式 */ +.el-button--primary.is-link { + color: #409eff; + + &:hover { + color: #66b1ff; + } +} + +.el-button--danger.is-link { + color: #f56c6c; + + &:hover { + color: #f78989; + } +} + + +/* 对话框表单样式 */ +.toolchain-form { + padding: 16px 0; + + .el-form-item { + margin-bottom: 24px; + + &:last-child { + margin-bottom: 0; + } + } + + .el-form-item__label { + font-weight: 500; + color: #606266; + } + + .el-input__inner { + height: 36px; + } + + .el-textarea__inner { + font-family: inherit; + line-height: 1.5; + } +} + +/* 路径输入组 */ +.path-input-group { + display: flex; + gap: 10px; + align-items: center; + + .el-input { + flex: 1; + } + + .el-button { + min-width: 80px; + } +} + +/* 对话框底部 */ +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 12px; +} + +/* 主按钮样式调整 */ +.el-button--primary { + background-color: #409eff; + border-color: #409eff; + + &:hover { + background-color: #66b1ff; + border-color: #66b1ff; + } +} + +/* 空数据状态 */ +.el-table__empty-text { + color: #909399; + font-size: 14px; + padding: 40px 0; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .content { + padding: 12px; + } + + .body-box { + padding: 16px; + } + + .header-section { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .page-title { + font-size: 18px; + } + + .header-actions { + width: 100%; + flex-direction: column; + + .el-button { + width: 100%; + } + } +} \ No newline at end of file diff --git a/src/vue/setting/view/toolchains/index.vue b/src/vue/setting/view/toolchains/index.vue new file mode 100644 index 0000000..12d26d4 --- /dev/null +++ b/src/vue/setting/view/toolchains/index.vue @@ -0,0 +1,225 @@ + + + + \ No newline at end of file diff --git a/src/vue/tsconfig.json b/src/vue/tsconfig.json new file mode 100644 index 0000000..b658c81 --- /dev/null +++ b/src/vue/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.vue" + ], + "exclude": [ + "vite.config.ts" + ] +} diff --git a/src/vue/tsconfig.node.json b/src/vue/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/src/vue/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/vue/vite.config.ts b/src/vue/vite.config.ts index c34a762..2708652 100644 --- a/src/vue/vite.config.ts +++ b/src/vue/vite.config.ts @@ -1,9 +1,9 @@ -import { defineConfig } from 'vite' -import vue from '@vitejs/plugin-vue' -import { resolve } from 'path' -import AutoImport from 'unplugin-auto-import/vite' -import Components from 'unplugin-vue-components/vite' -import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import { resolve } from 'path'; +import AutoImport from 'unplugin-auto-import/vite'; +import Components from 'unplugin-vue-components/vite'; +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; export default defineConfig({ plugins: [ @@ -16,27 +16,30 @@ export default defineConfig({ }), ], build: { + sourcemap: true, // 启用源映射以支持调试 rollupOptions: { input: { about: resolve(__dirname, 'about/index.html'), - home: resolve(__dirname, 'home/index.html'), + setting: resolve(__dirname, 'setting/index.html'), projects: resolve(__dirname, 'projects/index.html'), + 'create-project': resolve(__dirname, 'create-project/index.html'), }, output: { manualChunks: undefined, // 避免生成太多chunk + sourcemapExcludeSources: false, // 在源映射中包含源代码 } }, outDir: '../../out', assetsDir: 'assets', emptyOutDir: true, chunkSizeWarningLimit: 1000, - minify: 'terser', // 使用terser进行更好的压缩 + minify: false, // 开发时不压缩 terserOptions: { compress: { - drop_console: true, // 移除console.log + drop_console: false, // 不移除console.log drop_debugger: true, // 移除debugger }, }, cssCodeSplit: false, // 将CSS合并为单个文件 } -}) +}); diff --git a/src/webviews/about.ts b/src/webviews/about.ts index 244f336..dbb97ba 100644 --- a/src/webviews/about.ts +++ b/src/webviews/about.ts @@ -3,6 +3,7 @@ import * as os from 'os'; import * as fs from 'fs'; import * as marked from 'marked'; import * as path from 'path'; +import { postMessageExtensionData } from '../extension'; let aboutViewPanel: vscode.WebviewPanel | null = null; const name = "about"; @@ -37,6 +38,7 @@ export function openAboutWebview(context: vscode.ExtensionContext) { const rootDir = path.join(context.extensionPath, 'out'); const panel = vscode.window.createWebviewPanel('webview', title, vscode.ViewColumn.One, { enableScripts: true, // Enable javascript in the webview + retainContextWhenHidden: true, // Keep the webview's context when it is hidden localResourceRoots: [vscode.Uri.file(rootDir)] // Only allow resources from vue view }); const iconPath = path.join(context.extensionPath, 'resources', 'images', 'rt-thread.png'); @@ -83,5 +85,7 @@ export function openAboutWebview(context: vscode.ExtensionContext) { aboutViewPanel = panel; } + postMessageExtensionData(context, aboutViewPanel); + return aboutViewPanel; } diff --git a/src/webviews/create-project.ts b/src/webviews/create-project.ts new file mode 100644 index 0000000..9bddb34 --- /dev/null +++ b/src/webviews/create-project.ts @@ -0,0 +1,196 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { getEnvROOT, getExtensionVersion, createProject, readJsonObject, getBoardInfo } from '../api'; +import { postMessageExtensionData } from '../extension'; + +let createProjectViewPanel: vscode.WebviewPanel | null = null; +const name = "create-project"; +const title = "RT-Thread Create Project"; + +const cfgFn = path.join(os.homedir(), '.env/cfg.json'); +const sdkCfgFn = path.join(os.homedir(), '.env/tools/scripts/sdk_cfg.json'); + +let extensionInfo = { + version: "0.1.1", + env: { + path: "~/.env", + version: "0.0.1" + }, + projectList: [ + { + manufacturer: "ST", + boards: [ + "stm32f412-st-nucleo", + "stm32f407-rt-spark" + ] + }, + { + manufacturer: "QEMU", + boards: [ + "qemu-vexpress-a9", + "qemu-virt64-aarch64", + "qemu-virt64-riscv64" + ] + } + ], + SDKConfig: {}, + configInfo: [{ name: "RT-Thread", path: "d:/workspace/rt-thread", description: "RT-Thread主干路径" }] +}; + +function readReadmeFile(fn: string): string { + if (fs.existsSync(fn)) { + let data = fs.readFileSync(fn, 'utf-8'); + return data; + } + + return ""; +} + +export function openCreateProjectWebview(context: vscode.ExtensionContext) { + if (createProjectViewPanel) { + createProjectViewPanel.reveal(vscode.ViewColumn.One); + } + else { + const rootDir = path.join(context.extensionPath, 'out'); + const panel = vscode.window.createWebviewPanel('webview', title, vscode.ViewColumn.One, { + enableScripts: true, // Enable javascript in the webview + retainContextWhenHidden: true, // Keep the webview's context when it is hidden + localResourceRoots: [vscode.Uri.file(rootDir)] // Only allow resources from vue view + }); + + // 更新扩展版本信息 + extensionInfo.version = getExtensionVersion(); + + // 更新配置信息 + let cfgObj = readJsonObject(cfgFn); + if (cfgObj) { + extensionInfo.configInfo = cfgObj.RTThreadConfig; + } + + // 更新SDK相关信息 + let sdkCfgObj = readJsonObject(sdkCfgFn); + if (sdkCfgObj) { + extensionInfo.SDKConfig = sdkCfgObj; + } + + // And set its HTML content + // read out/${name}/index.html + const indexHtmlPath = vscode.Uri.file(context.asAbsolutePath(`out/${name}/index.html`)); + const htmlFolder = vscode.Uri.file(context.asAbsolutePath(`out`)); + const indexHtmlContent = vscode.workspace.fs.readFile(indexHtmlPath).then(buffer => buffer.toString()); + + // set html + indexHtmlContent.then(content => { + panel.webview.html = content.replace(/"[\w\-\.\/]+?\.(?:css|js)"/ig, (str) => { + const fileName = str.substr(1, str.length - 2); // remove '"' + const absPath = htmlFolder.path + '/' + fileName; + const uri = vscode.Uri.file(absPath); + return '"' + panel.webview.asWebviewUri(uri).toString() + '"'; + }); + }); + createProjectViewPanel = panel; + + // Handle messages from the webview + panel.webview.onDidReceiveMessage( + message => { + switch (message.command) { + case 'getExtensionInfo': + // 尝试读取env环境变量信息并获取各种路径信息 + let envRoot = getEnvROOT(); + extensionInfo.env.path = envRoot; + + // 更新环境版本信息 + if (fs.existsSync(path.join(extensionInfo.env.path, 'tools', 'scripts', 'env.json'))) { + let envInfo = readJsonObject(path.join(extensionInfo.env.path, 'tools', 'scripts', 'env.json')); + if (envInfo) { + extensionInfo.env.version = envInfo.version; + } + } + + let workspace = vscode.workspace.workspaceFolders?.[0]; + // 获取BSP工程信息 + if (workspace) { + const rtthreadFn = path.join(workspace.uri.fsPath, '.vscode', 'rtthread.json'); + if (fs.existsSync(rtthreadFn)) { + let rtthreadObj = readJsonObject(rtthreadFn); + if (rtthreadObj && rtthreadObj.board_info) { + extensionInfo.projectList = getBoardInfo(); + } + } + } + + // 如果没有从workspace获取到projectList,则使用默认值 + if (!extensionInfo.projectList || extensionInfo.projectList.length === 0) { + extensionInfo.projectList = [ + { + manufacturer: "ST", + boards: [ + "stm32f412-st-nucleo", + "stm32f407-rt-spark" + ] + }, + { + manufacturer: "QEMU", + boards: [ + "qemu-vexpress-a9", + "qemu-virt64-aarch64", + "qemu-virt64-riscv64" + ] + } + ]; + } + + panel.webview.postMessage({ command: 'extensionInfo', data: extensionInfo }); + return; + + case 'createProject': + if (message.args && message.args.length > 0) { + const project = message.args[0]; + + // 调用创建工程的API - 参数顺序: folder, projectInfo + createProject(project.folder, project); + } + return; + + case 'browseProjectFolder': + const defaultPath = message.args && message.args.length > 0 ? message.args[0] : undefined; + vscode.window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + defaultUri: defaultPath ? vscode.Uri.file(defaultPath) : undefined, + openLabel: '选择文件夹' + }).then(result => { + if (result && result.length > 0) { + const selectedPath = result[0].fsPath; + panel.webview.postMessage({ command: 'setProjectFolder', data: selectedPath }); + } + }); + return; + } + }, + undefined, + context.subscriptions + ); + + // 当webview被关闭时,重置panel引用 + panel.onDidDispose(() => { + createProjectViewPanel = null; + }, null, context.subscriptions); + } + + postMessageExtensionData(context, createProjectViewPanel); +} + +export function isCreateProjectWebviewActive(): boolean { + return createProjectViewPanel !== null; +} + +export function disposeCreateProjectWebview() { + if (createProjectViewPanel) { + createProjectViewPanel.dispose(); + createProjectViewPanel = null; + } +} diff --git a/src/webviews/project.ts b/src/webviews/project.ts index 6723373..431f16a 100644 --- a/src/webviews/project.ts +++ b/src/webviews/project.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import { getWorkspaceFolder } from '../api'; +import { postMessageExtensionData } from '../extension'; let workspaceViewPanel: vscode.WebviewPanel | null = null; const name = "projects"; @@ -97,6 +98,7 @@ export function openWorkspaceProjectsWebview(context: vscode.ExtensionContext) { const rootDir = path.join(context.extensionPath, 'out'); const panel = vscode.window.createWebviewPanel('webview', title, vscode.ViewColumn.One, { enableScripts: true, // Enable javascript in the webview + retainContextWhenHidden: true, // Keep the webview's context when it is hidden localResourceRoots: [vscode.Uri.file(rootDir)] // Only allow resources from vue view }); const iconPath = path.join(context.extensionPath, 'resources', 'images', 'rt-thread.png'); @@ -165,6 +167,8 @@ export function openWorkspaceProjectsWebview(context: vscode.ExtensionContext) { workspaceViewPanel = panel; } + postMessageExtensionData(context, workspaceViewPanel); + return workspaceViewPanel; } diff --git a/src/webviews/home.ts b/src/webviews/setting.ts similarity index 68% rename from src/webviews/home.ts rename to src/webviews/setting.ts index 6c7843d..d26ae31 100644 --- a/src/webviews/home.ts +++ b/src/webviews/setting.ts @@ -2,11 +2,15 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; -import { getEnvROOT, getExtensionVersion, installEnv, createProject, readJsonObject, openFolder, writeJsonObject, getBoardInfo } from '../api'; +import { spawn } from 'child_process'; +import { getEnvROOT, getExtensionVersion, installEnv, readJsonObject, openFolder, writeJsonObject } from '../api'; +import { handleSDKMessage } from './settings_sdk'; +import { checkEnvStatus, handleEnvMessage } from './settings_env'; +import { postMessageExtensionData } from '../extension'; -let homeViewPanel: vscode.WebviewPanel | null = null; -const name = "home"; -const title = "RT-Thread Home"; +let settingViewPanel: vscode.WebviewPanel | null = null; +const name = "setting"; +const title = "RT-Thread Setting"; const cfgFn = path.join(os.homedir(), '.env/cfg.json'); const sdkCfgFn = path.join(os.homedir(), '.env/tools/scripts/sdk_cfg.json'); @@ -17,23 +21,6 @@ let extensionInfo = { path: "~/.env", version: "0.0.1" }, - projectList: [ - { - manufacturer: "ST", - boards: [ - "stm32f412-st-nucleo", - "stm32f407-rt-spark" - ] - }, - { - manufacturer: "QEMU", - boards: [ - "qemu-vexpress-a9", - "qemu-virt64-aarch64", - "qemu-virt64-riscv64" - ] - } - ], SDKConfig : {}, configInfo : [{name: "RT-Thread", path: "d:/workspace/rt-thread", description: "RT-Thread主干路径"}] }; @@ -53,15 +40,18 @@ function readBoardInfoFile() { return ; } -export function openHomeWebview(context: vscode.ExtensionContext) { - if (homeViewPanel) { - homeViewPanel.reveal(vscode.ViewColumn.One); +// Env 相关函数已移动到 settings_env.ts + +export function openSettingWebview(context: vscode.ExtensionContext) { + if (settingViewPanel) { + settingViewPanel.reveal(vscode.ViewColumn.One); } else { const rootDir = path.join(context.extensionPath, 'out'); const panel = vscode.window.createWebviewPanel('webview', title, vscode.ViewColumn.One, { enableScripts: true, // Enable javascript in the webview - localResourceRoots: [vscode.Uri.file(rootDir)] // Only allow resources from vue view + localResourceRoots: [vscode.Uri.file(rootDir)], // Only allow resources from vue view, + retainContextWhenHidden: true, // Keep the webview's context when it is hidden }); let localResourceRoots = vscode.Uri.joinPath(context.extensionUri, 'out').toString(); // console.log(localResourceRoots); @@ -71,7 +61,7 @@ export function openHomeWebview(context: vscode.ExtensionContext) { // handle close webview event panel.onDidDispose(() => { - homeViewPanel = null; + settingViewPanel = null; }); // update extensionInfo @@ -83,15 +73,25 @@ export function openHomeWebview(context: vscode.ExtensionContext) { extensionInfo.env.version = envInfo.version; } extensionInfo.SDKConfig = readJsonObject(sdkCfgFn); - extensionInfo.projectList = getBoardInfo(); - // console.log(extensionInfo.projectList); + // read RT-Thread folder let cfg:any [] = readJsonObject(cfgFn); - if (cfg.length > 0) { + if (cfg.length > 0 && cfg[0].path) { extensionInfo.configInfo[0].path = cfg[0].path; } else { - extensionInfo.configInfo[0].path = 'undefined'; + // 设置为空字符串而不是字符串'undefined' + extensionInfo.configInfo[0].path = ''; + } + + // 初始化时检查 Env 状态 + checkEnvStatus().then(envStatus => { + panel.webview.postMessage({ command: 'envStatus', status: envStatus }); + }); + + // 初始化时发送SDK配置 + if (extensionInfo.SDKConfig) { + panel.webview.postMessage({ command: 'setSDKConfig', data: extensionInfo.SDKConfig }); } // read out/${name}/index.html @@ -107,8 +107,28 @@ export function openHomeWebview(context: vscode.ExtensionContext) { return `"${panel.webview.asWebviewUri(vscode.Uri.file(absPath)).toString()}"`; }); + + // 在HTML设置完成后延迟发送初始数据 + setTimeout(() => { + // 发送SDK配置 + if (extensionInfo.SDKConfig) { + panel.webview.postMessage({ command: 'setSDKConfig', data: extensionInfo.SDKConfig }); + } + // 发送扩展信息 + panel.webview.postMessage({command: 'extensionInfo', data: extensionInfo}); + }, 100); }); panel.webview.onDidReceiveMessage(async (message) => { + // 先尝试使用SDK处理器处理消息 + if (handleSDKMessage(panel.webview, message)) { + return; + } + + // 尝试使用Env处理器处理消息 + if (handleEnvMessage(panel.webview, message)) { + return; + } + let data : any = {}; let defaultPath:any; @@ -116,17 +136,6 @@ export function openHomeWebview(context: vscode.ExtensionContext) { case 'getExtensionInfo': panel.webview.postMessage({command: 'extensionInfo', data: extensionInfo}); return ; - case 'createProject': - let projectInfo = message.args[0]; - createProject(projectInfo.folder, projectInfo); - return; - case 'browseProjectFolder': - defaultPath = message.args[0]; - let projectFolder = await openFolder(defaultPath); - if (projectFolder) { - panel.webview.postMessage({command: 'setProjectFolder', data: projectFolder}); - } - return ; case 'browseToolchainFolder': defaultPath = message.args[0]; let folder = await openFolder(defaultPath); @@ -146,12 +155,14 @@ export function openHomeWebview(context: vscode.ExtensionContext) { case 'getSDkConfig': data = readJsonObject(sdkCfgFn); if (data) { - panel.webview.postMessage({command: 'setConfig', data: data}); + panel.webview.postMessage({command: 'setSDKConfig', data: data}); } return ; case 'setSDKConfig': data = message.args[0]; writeJsonObject(message.args[0], sdkCfgFn); + // 更新内存中的配置 + extensionInfo.SDKConfig = data; vscode.window.showInformationMessage('保存工具链配置成功'); return ; @@ -169,7 +180,11 @@ export function openHomeWebview(context: vscode.ExtensionContext) { vscode.window.showInformationMessage('保存路径配置成功'); // update configData - extensionInfo.configInfo[0].path = data[0].path; + if (data && data.length > 0 && data[0].path) { + extensionInfo.configInfo[0].path = data[0].path; + } else { + extensionInfo.configInfo[0].path = ''; + } return ; case 'getBoardReadme': @@ -191,10 +206,12 @@ export function openHomeWebview(context: vscode.ExtensionContext) { if (e.webviewPanel.visible) { panel.webview.postMessage({command: 'extensionInfo', data: extensionInfo}); } - }) + }); - homeViewPanel = panel; + settingViewPanel = panel; } - return homeViewPanel; + postMessageExtensionData(context, settingViewPanel); + + return settingViewPanel; } diff --git a/src/webviews/settings_env.ts b/src/webviews/settings_env.ts new file mode 100644 index 0000000..297ddec --- /dev/null +++ b/src/webviews/settings_env.ts @@ -0,0 +1,361 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { spawn } from 'child_process'; + +// Env 相关函数 +export async function checkEnvStatus(): Promise<{ installed: boolean; path: string; envVersion?: string; envGitRev?: string }> { + const homeDir = os.homedir(); + const envPath = path.join(homeDir, '.env'); + const installed = fs.existsSync(envPath); + let envVersion: string | undefined = undefined; + let envGitRev: string | undefined = undefined; + if (installed) { + // 读取env.json + try { + const envJsonPath = path.join(envPath, 'tools', 'scripts', 'env.json'); + if (fs.existsSync(envJsonPath)) { + const envJson = JSON.parse(fs.readFileSync(envJsonPath, 'utf-8')); + envVersion = envJson.version; + } + } catch {} + // 获取git修订 + try { + const { spawnSync } = require('child_process'); + const result = spawnSync('git', ['-C', path.join(envPath, 'tools', 'scripts'), 'rev-parse', 'HEAD']); + if (result.status === 0 && result.stdout) { + envGitRev = result.stdout.toString().trim(); + } + } catch {} + } + return { installed, path: envPath, envVersion, envGitRev }; +} + +export async function installEnvFunction(webview: vscode.Webview) { + // 清除之前的进度日志 + webview.postMessage({ command: 'clearProgress' }); + + const platform = process.platform; + if (platform === 'win32') { + await installWindowsEnv(webview); + } else { + await installLinuxEnv(webview); + } +} + +async function installLinuxEnv(webview: vscode.Webview) { + const homeDir = os.homedir(); + const envPath = path.join(homeDir, '.env'); + + if (fs.existsSync(envPath)) { + webview.postMessage({ + command: 'installProgress', + type: 'error', + message: 'Env 已安装在 ' + envPath + }); + return; + } + + // 判断地理位置 + const location = await getLocation(); + const giteeArg = location === 'CN' ? ' --gitee' : ''; + const installScriptUrl = location === 'CN' + ? 'https://gitee.com/RT-Thread-Mirror/env/raw/master/install_ubuntu.sh' + : 'https://raw.githubusercontent.com/RT-Thread/env/master/install_ubuntu.sh'; + const scriptPath = path.join(os.tmpdir(), 'install_ubuntu.sh'); + + try { + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '正在下载安装脚本...' + }); + await executeCommand(`wget "${installScriptUrl}" -O "${scriptPath}"`, webview); + + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '赋予安装脚本执行权限...' + }); + await executeCommand(`chmod 777 "${scriptPath}"`, webview); + + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: `正在执行安装脚本${giteeArg ? '(--gitee)' : ''}...` + }); + await executeCommand(`${scriptPath}${giteeArg}`, webview); + + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '清理安装脚本...' + }); + await executeCommand(`rm "${scriptPath}"`, webview); + + webview.postMessage({ + command: 'installProgress', + type: 'success', + message: 'RT-Thread Env 安装成功!' + }); + + const envStatus = await checkEnvStatus(); + webview.postMessage({ command: 'envStatus', status: envStatus }); + + } catch (error) { + webview.postMessage({ + command: 'installProgress', + type: 'error', + message: `安装失败: ${error}` + }); + } +} + +async function installWindowsEnv(webview: vscode.Webview) { + const homeDir = os.homedir(); + const envPath = path.join(homeDir, '.env'); + + if (fs.existsSync(envPath)) { + webview.postMessage({ + command: 'installProgress', + type: 'error', + message: 'Env 已安装在 ' + envPath + }); + return; + } + + // 判断地理位置 + const location = await getLocation(); + const giteeArg = location === 'CN' ? ' --gitee' : ''; + const installScriptUrl = location === 'CN' + ? 'https://gitee.com/RT-Thread-Mirror/env/raw/master/install_windows.ps1' + : 'https://raw.githubusercontent.com/RT-Thread/env/master/install_windows.ps1'; + const scriptPath = path.join(os.tmpdir(), 'install_windows.ps1'); + + try { + // Show info message + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '开始安装 RT-Thread Env...' + }); + + try { + // Step 1: Download installation script + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '步骤 1: 正在下载安装脚本...' + }); + await executeCommand( + `powershell -Command wget "${installScriptUrl}" -O install_windows.ps1`, + webview + ); + + // Step 2: Set execution policy + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '步骤 2: 正在设置执行策略...' + }); + await executeCommand( + 'powershell -Command Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force', + webview + ); + + // Step 3: Run installation script + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '步骤 3: 正在运行安装脚本(自动模式)...' + }); + await executeCommand( + `powershell -Command .\\install_windows.ps1 ${giteeArg} -y`, + webview + ); + + // Step 4: Activate virtual environment + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '步骤 4: 正在激活虚拟环境...' + }); + await executeCommand( + `powershell -Command ~\\.env\\env.ps1`, + webview + ); + + // Step 5: Clean up installation script + webview.postMessage({ + command: 'installProgress', + type: 'info', + message: '步骤 5: 正在清理安装脚本...' + }); + await executeCommand(`powershell -Command del .\\install_windows.ps1`, webview); + + webview.postMessage({ + command: 'installProgress', + type: 'success', + message: 'RT-Thread Env 安装成功完成!' + }); + + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + // vscode.window.showErrorMessage(`RT-Thread Env installation failed: ${errorMessage}`); + + webview.postMessage({ + command: 'installProgress', + type: 'error', + message: `RT-Thread Env 安装失败: ${errorMessage}` + }); + } + + const envStatus = await checkEnvStatus(); + webview.postMessage({ command: 'envStatus', status: envStatus }); + } catch (error) { + webview.postMessage({ + command: 'installProgress', + type: 'error', + message: `安装失败: ${error}` + }); + } +} + +export async function updateEnvFunction(webview: vscode.Webview) { + // 清除之前的进度日志 + webview.postMessage({ command: 'clearProgress' }); + + const homeDir = os.homedir(); + const scriptsDir = path.join(homeDir, '.env', 'tools', 'scripts'); + const packagesDir = path.join(homeDir, '.env', 'packages', 'packages'); + const sdkDir = path.join(homeDir, '.env', 'packages', 'sdk'); + try { + webview.postMessage({ command: 'installProgress', type: 'info', message: '正在更新 ~/.env/tools/scripts ...' }); + await executeCommand('git pull origin', webview, scriptsDir); + webview.postMessage({ command: 'installProgress', type: 'info', message: '正在更新 ~/.env/packages/packages ...' }); + await executeCommand('git pull origin', webview, packagesDir); + webview.postMessage({ command: 'installProgress', type: 'info', message: '正在更新 ~/.env/packages/sdk ...' }); + await executeCommand('git pull origin', webview, sdkDir); + webview.postMessage({ command: 'installProgress', type: 'success', message: 'Env 更新完成!' }); + } catch (error) { + webview.postMessage({ command: 'installProgress', type: 'error', message: `Env 更新失败: ${error}` }); + } +} + +export async function deleteEnvFunction(webview: vscode.Webview) { + // 清除之前的进度日志 + webview.postMessage({ command: 'clearProgress' }); + + const homeDir = os.homedir(); + const envPath = path.join(homeDir, '.env'); + + // 检查路径是否存在 + if (!fs.existsSync(envPath)) { + webview.postMessage({ command: 'installProgress', type: 'warning', message: 'Env 目录不存在,无需删除。' }); + const envStatus = await checkEnvStatus(); + webview.postMessage({ command: 'envStatus', status: envStatus }); + return; + } + + try { + webview.postMessage({ command: 'installProgress', type: 'info', message: '正在删除 ~/.env ...' }); + await deleteDirRecursive(envPath); + webview.postMessage({ command: 'installProgress', type: 'success', message: 'Env 已成功删除。' }); + + // 重新检查状态 + const envStatus = await checkEnvStatus(); + webview.postMessage({ command: 'envStatus', status: envStatus }); + } catch (error) { + webview.postMessage({ command: 'installProgress', type: 'error', message: `删除失败: ${error}` }); + } +} + +async function deleteDirRecursive(dirPath: string): Promise { + return new Promise((resolve, reject) => { + fs.rm(dirPath, { recursive: true, force: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +function executeCommand(command: string, webview: vscode.Webview, cwd?: string): Promise { + return new Promise((resolve, reject) => { + const parts = command.split(' '); + const cmd = parts[0]; + const args = parts.slice(1); + const proc = spawn(cmd, args, { shell: true, cwd }); + + webview.postMessage({ command: 'installProgress', type: 'info', message: command }); + proc.stdout.on('data', (data: Buffer) => { + webview.postMessage({ command: 'installProgress', type: 'info', message: data.toString() }); + }); + proc.stderr.on('data', (data: Buffer) => { + const msg = data.toString(); + webview.postMessage({ command: 'installProgress', type: 'warning', message: msg }); + }); + proc.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + webview.postMessage({ command: 'installProgress', type: 'error', message: `命令执行失败,退出代码 ${code}` }); + reject(new Error(`Command exited with code ${code}`)); + } + }); + proc.on('error', (err) => { + webview.postMessage({ command: 'installProgress', type: 'error', message: `命令执行失败: ${err.message}` }); + reject(err); + }); + }); +} + +async function getLocation(): Promise<'CN' | 'GLOBAL'> { + // 使用ipinfo.io等公共API判断IP归属地 + try { + const https = require('https'); + return await new Promise((resolve) => { + https.get('https://ipinfo.io/json', (resp: any) => { + let data = ''; + resp.on('data', (chunk: any) => { data += chunk; }); + resp.on('end', () => { + try { + const info = JSON.parse(data); + if (info && info.country === 'CN') { + resolve('CN'); + } else { + resolve('GLOBAL'); + } + } catch { + resolve('GLOBAL'); + } + }); + }).on('error', () => { + resolve('GLOBAL'); + }); + }); + } catch { + return 'GLOBAL'; + } +} + +// 处理 Env 相关消息 +export function handleEnvMessage(webview: vscode.Webview, message: any): boolean { + switch (message.command) { + case 'checkEnvStatus': + checkEnvStatus().then(envStatus => { + webview.postMessage({ command: 'envStatus', status: envStatus }); + }); + return true; + case 'installEnv': + installEnvFunction(webview); + return true; + case 'updateEnv': + updateEnvFunction(webview); + return true; + case 'deleteEnv': + deleteEnvFunction(webview); + return true; + } + return false; +} \ No newline at end of file diff --git a/src/webviews/settings_sdk.ts b/src/webviews/settings_sdk.ts new file mode 100644 index 0000000..5cc726e --- /dev/null +++ b/src/webviews/settings_sdk.ts @@ -0,0 +1,458 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { spawn } from 'child_process'; + +// SDK版本信息接口 +interface SDKVersion { + version: string; + URL: string; +} + +// SDK包信息接口 +interface SDKPackage { + name: string; + description: string; + site: SDKVersion[]; +} + +// SDK配置信息接口 +interface SDKConfig { + name: string; + installed: boolean; + installedVersion?: string; + path?: string; +} + +// SDK列表项接口 +interface SDKListItem { + name: string; + description: string; + versions: string[]; + installedVersion?: string; + installed: boolean; + path?: string; +} + +// 获取SDK目录路径 +function getSDKDirectory(): string { + const envPath = path.join(os.homedir(), '.env', 'packages', 'sdk'); + const platform = os.platform(); + + if (platform === 'linux') { + return path.join(envPath, 'Linux'); + } else if (platform === 'win32') { + return path.join(envPath, 'Windows'); + } + + return envPath; +} + +// 获取配置文件路径 +function getConfigPath(): string { + return path.join(os.homedir(), '.env', 'tools', 'scripts', '.config'); +} + +// 读取SDK包信息 +async function readSDKPackage(sdkPath: string): Promise { + try { + const packageJsonPath = path.join(sdkPath, 'package.json'); + if (!fs.existsSync(packageJsonPath)) { + return null; + } + + const content = fs.readFileSync(packageJsonPath, 'utf8'); + const packageData = JSON.parse(content); + + return { + name: packageData.name || path.basename(sdkPath), + description: packageData.description || '', + site: packageData.site || [] + }; + } catch (error) { + console.error(`Failed to read package.json from ${sdkPath}:`, error); + return null; + } +} + +// 读取.config文件中的SDK配置 +function readSDKConfigs(): Map { + const configs = new Map(); + const configPath = getConfigPath(); + + if (!fs.existsSync(configPath)) { + return configs; + } + + try { + const content = fs.readFileSync(configPath, 'utf8'); + const lines = content.split('\n'); + + // 解析配置文件 + const configData: { [key: string]: string } = {}; + for (const line of lines) { + const match = line.match(/^(CONFIG_[A-Z0-9_]+)=(.*)$/); + if (match) { + const key = match[1]; + const value = match[2].replace(/^"(.*)"$/, '$1'); // 去除引号 + configData[key] = value; + } + } + + // 查找所有已安装的SDK + for (const key in configData) { + if (key.startsWith('CONFIG_PKG_USING_') && configData[key] === 'y') { + const upperName = key.substring('CONFIG_PKG_USING_'.length); + // 将下划线转换回连字符,并转为小写 + const sdkName = upperName.replace(/_/g, '-').toLowerCase(); + + const config: SDKConfig = { + name: sdkName, + installed: true + }; + + // 获取版本 + const versionKey = `CONFIG_PKG_${upperName}_VER`; + if (configData[versionKey]) { + config.installedVersion = configData[versionKey]; + } + + // 获取路径 + const pathKey = `CONFIG_PKG_${upperName}_PATH`; + if (configData[pathKey]) { + config.path = configData[pathKey]; + } + + configs.set(sdkName, config); + } + } + } catch (error) { + console.error('Failed to read .config file:', error); + } + + return configs; +} + +// 获取SDK列表 +export async function getSDKList(): Promise { + const sdkDir = getSDKDirectory(); + const sdkList: SDKListItem[] = []; + + if (!fs.existsSync(sdkDir)) { + console.warn(`SDK directory not found: ${sdkDir}`); + // 返回空列表 + return []; + } + + // 读取已安装的SDK配置 + const installedConfigs = readSDKConfigs(); + + // 扫描SDK目录 + const sdkDirs = fs.readdirSync(sdkDir).filter(name => { + const fullPath = path.join(sdkDir, name); + return fs.statSync(fullPath).isDirectory(); + }); + + for (const sdkName of sdkDirs) { + const sdkPath = path.join(sdkDir, sdkName); + const packageInfo = await readSDKPackage(sdkPath); + + if (packageInfo) { + const config = installedConfigs.get(sdkName.toLowerCase()); + const versions = packageInfo.site.map(s => s.version); + + sdkList.push({ + name: sdkName, + description: packageInfo.description, + versions: versions, + installed: config?.installed || false, + installedVersion: config?.installedVersion, + path: config?.path + }); + } + } + + return sdkList; +} + +// 更新.config文件 +export async function updateSDKConfig(configs: Array<{ name: string; version: string; install: boolean }>): Promise { + const configPath = getConfigPath(); + let content = ''; + + // 读取现有配置 + if (fs.existsSync(configPath)) { + content = fs.readFileSync(configPath, 'utf8'); + } else { + // 创建默认配置头 + content = 'CONFIG_TARGET_FILE=""\n'; + } + + // 更新配置 + for (const config of configs) { + const upperName = config.name.replace(/-/g, '_').toUpperCase(); + const usingKey = `CONFIG_PKG_USING_${upperName}`; + const versionKey = `CONFIG_PKG_${upperName}_VER`; + const pathKey = `CONFIG_PKG_${upperName}_PATH`; + + if (config.install) { + // 添加或更新配置 + content = updateConfigLine(content, usingKey, 'y'); + content = updateConfigLine(content, versionKey, `"${config.version}"`); + + // 设置默认路径(可以根据实际需求调整) + const defaultPath = path.join(os.homedir(), '.env', 'tools', config.name); + content = updateConfigLine(content, pathKey, `"${defaultPath}"`); + } else { + // 移除配置 + content = removeConfigLine(content, usingKey); + content = removeConfigLine(content, versionKey); + content = removeConfigLine(content, pathKey); + } + } + + // 确保目录存在 + const configDir = path.dirname(configPath); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + + // 写入配置文件 + fs.writeFileSync(configPath, content, 'utf8'); +} + +// 更新配置行 +function updateConfigLine(content: string, key: string, value: string): string { + const regex = new RegExp(`^${key}=.*$`, 'm'); + const newLine = `${key}=${value}`; + + if (regex.test(content)) { + // 更新现有行 + return content.replace(regex, newLine); + } else { + // 添加新行 + return content + '\n' + newLine; + } +} + +// 移除配置行 +function removeConfigLine(content: string, key: string): string { + const regex = new RegExp(`^${key}=.*$\\n?`, 'gm'); + return content.replace(regex, ''); +} + +// 保存完整的SDK配置到.config文件 +export async function saveSDKDotConfig(configs: Array<{ name: string; version: string; selected: boolean; path: string; versions?: string[] }>): Promise { + const configPath = getConfigPath(); + let content = 'CONFIG_TARGET_FILE=""\n'; + + // 生成所有SDK的配置 + for (const config of configs) { + const upperName = config.name.replace(/-/g, '_').toUpperCase(); + const usingKey = `CONFIG_PKG_USING_${upperName}`; + + if (config.selected && config.version) { + // SDK已选择 + content += `${usingKey}=y\n`; + + // 设置路径 - 使用相对路径格式 + const platform = os.platform() === 'win32' ? 'Windows' : 'Linux'; + const sdkPath = config.path || `sdk/${platform}/${config.name}`; + content += `CONFIG_PKG_${upperName}_PATH="${sdkPath}"\n`; + + // 添加版本选择标记 + if (config.versions && config.versions.length > 0) { + for (const ver of config.versions) { + // 将版本号转换为大写,移除点和横线 + const versionKey = `CONFIG_PKG_USING_${upperName}_${ver.replace(/[\.-]/g, '').toUpperCase()}`; + if (ver === config.version) { + content += `${versionKey}=y\n`; + } else { + content += `# ${versionKey} is not set\n`; + } + } + } + + // 设置版本 + content += `CONFIG_PKG_${upperName}_VER="${config.version}"\n`; + } else { + // SDK未选择 + content += `# ${usingKey} is not set\n`; + } + } + + // 确保目录存在 + const configDir = path.dirname(configPath); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + } + + // 写入配置文件 + fs.writeFileSync(configPath, content, 'utf8'); +} + +// 当前运行的env进程 +let currentEnvProcess: any = null; + +// 执行 env.py pkgs --update-force 命令 +export async function runEnvPkgsUpdate(webview: vscode.Webview): Promise { + const envScriptPath = path.join(os.homedir(), '.env', 'tools', 'scripts', 'env.py'); + + console.log('Running env.py from:', envScriptPath); + + // 检查脚本是否存在 + if (!fs.existsSync(envScriptPath)) { + const errorMsg = `Error: env.py script not found at ${envScriptPath}`; + console.error(errorMsg); + webview.postMessage({ + command: 'sdkUpdateLog', + log: errorMsg + '\n' + }); + webview.postMessage({ + command: 'sdkUpdateFinished', + success: false + }); + return; + } + + // 发送开始消息 + webview.postMessage({ + command: 'sdkUpdateStarted' + }); + + // 执行脚本 + const pythonCmd = os.platform() === 'win32' ? 'python' : 'python3'; + currentEnvProcess = spawn(pythonCmd, [envScriptPath, 'pkgs', '--update-force'], { + cwd: path.dirname(envScriptPath), + env: { ...process.env } + }); + + // 实时输出日志 + currentEnvProcess.stdout.on('data', (data: Buffer) => { + webview.postMessage({ + command: 'sdkUpdateLog', + log: data.toString() + }); + }); + + currentEnvProcess.stderr.on('data', (data: Buffer) => { + webview.postMessage({ + command: 'sdkUpdateLog', + log: data.toString() + }); + }); + + // 进程结束 + currentEnvProcess.on('close', (code: number | null) => { + currentEnvProcess = null; + webview.postMessage({ + command: 'sdkUpdateFinished', + success: code === 0 + }); + }); + + currentEnvProcess.on('error', (error: Error) => { + currentEnvProcess = null; + webview.postMessage({ + command: 'sdkUpdateLog', + log: `Error: ${error.message}` + }); + webview.postMessage({ + command: 'sdkUpdateFinished', + success: false + }); + }); +} + +// 取消SDK更新 +export function cancelSDKUpdate(): void { + if (currentEnvProcess) { + console.log('Cancelling SDK update process...'); + // 尝试正常终止进程 + currentEnvProcess.kill('SIGTERM'); + + // 如果在Windows上,使用taskkill + if (os.platform() === 'win32') { + spawn('taskkill', ['/F', '/T', '/PID', currentEnvProcess.pid.toString()]); + } + + // 5秒后强制终止 + setTimeout(() => { + if (currentEnvProcess) { + currentEnvProcess.kill('SIGKILL'); + currentEnvProcess = null; + } + }, 5000); + } +} + +// 处理SDK相关消息 +export function handleSDKMessage(webview: vscode.Webview, message: any): boolean { + switch (message.command) { + case 'getSDKList': + getSDKList().then(sdkList => { + webview.postMessage({ + command: 'setSDKList', + data: sdkList + }); + }).catch(error => { + console.error('Failed to get SDK list:', error); + webview.postMessage({ + command: 'sdkConfigError', + error: '获取SDK列表失败' + }); + }); + return true; + + case 'saveSDKDotConfig': + if (message.args && message.args[0] && Array.isArray(message.args[0])) { + saveSDKDotConfig(message.args[0]).then(() => { + // vscode.window.showInformationMessage('SDK配置已保存到 .config 文件'); + // 执行 env.py pkgs --update-force + runEnvPkgsUpdate(webview); + }).catch(error => { + console.error('Failed to save SDK config:', error); + vscode.window.showErrorMessage(`保存SDK配置失败: ${error.message}`); + webview.postMessage({ + command: 'sdkConfigError', + error: `保存SDK配置失败: ${error.message}` + }); + }); + } else { + console.error('Invalid data format for saveSDKDotConfig:', message); + vscode.window.showErrorMessage('SDK配置数据格式错误'); + webview.postMessage({ + command: 'sdkConfigError', + error: 'SDK配置数据格式错误' + }); + } + return true; + + case 'applySDKConfig': + if (message.data && Array.isArray(message.data)) { + updateSDKConfig(message.data).then(() => { + webview.postMessage({ + command: 'sdkConfigApplied' + }); + + // TODO: 触发实际的SDK安装/卸载操作 + // 这里可以调用env工具的相关命令 + + }).catch(error => { + console.error('Failed to update SDK config:', error); + webview.postMessage({ + command: 'sdkConfigError', + error: '更新SDK配置失败' + }); + }); + } + return true; + + case 'cancelSDKUpdate': + cancelSDKUpdate(); + return true; + } + + return false; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0a222ab..45b402c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,5 @@ "rootDir": "src", "strict": true, }, - "exclude": ["node_modules", "out", "src/vue", "src/test"] + "exclude": ["node_modules", "out", "src/vue", "src/test", "ref"] }