Skip to content

扩展开发指南

本指南介绍如何为 Trae IDE 开发扩展,包括扩展架构、API 使用、最佳实践和发布流程。

概述

Trae IDE 扩展系统允许开发者:

  • 添加新的编辑器功能
  • 集成外部工具和服务
  • 自定义用户界面
  • 扩展语言支持
  • 创建主题和图标包
  • 实现自定义调试器

扩展架构

扩展类型

语言扩展

json
{
  "name": "my-language-support",
  "displayName": "My Language Support",
  "description": "Language support for MyLang",
  "version": "1.0.0",
  "engines": {
    "trae": "^1.0.0"
  },
  "categories": ["Programming Languages"],
  "contributes": {
    "languages": [{
      "id": "mylang",
      "aliases": ["MyLang", "mylang"],
      "extensions": [".ml", ".mylang"],
      "configuration": "./language-configuration.json"
    }],
    "grammars": [{
      "language": "mylang",
      "scopeName": "source.mylang",
      "path": "./syntaxes/mylang.tmGrammar.json"
    }]
  }
}

主题扩展

json
{
  "name": "my-theme",
  "displayName": "My Beautiful Theme",
  "description": "A beautiful dark theme",
  "version": "1.0.0",
  "engines": {
    "trae": "^1.0.0"
  },
  "categories": ["Themes"],
  "contributes": {
    "themes": [{
      "label": "My Beautiful Dark",
      "uiTheme": "vs-dark",
      "path": "./themes/my-beautiful-dark.json"
    }]
  }
}

功能扩展

json
{
  "name": "my-extension",
  "displayName": "My Extension",
  "description": "Adds awesome functionality",
  "version": "1.0.0",
  "engines": {
    "trae": "^1.0.0"
  },
  "categories": ["Other"],
  "activationEvents": [
    "onCommand:myExtension.helloWorld",
    "onLanguage:javascript"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [{
      "command": "myExtension.helloWorld",
      "title": "Hello World"
    }],
    "menus": {
      "commandPalette": [{
        "command": "myExtension.helloWorld",
        "when": "editorTextFocus"
      }]
    }
  }
}

扩展清单 (package.json)

json
{
  "name": "my-extension",
  "displayName": "My Extension",
  "description": "Extension description",
  "version": "1.0.0",
  "publisher": "your-publisher-name",
  "engines": {
    "trae": "^1.0.0"
  },
  "categories": [
    "Programming Languages",
    "Themes",
    "Debuggers",
    "Formatters",
    "Linters",
    "Snippets",
    "Keymaps",
    "Other"
  ],
  "keywords": [
    "javascript",
    "typescript",
    "react"
  ],
  "activationEvents": [
    "onCommand:myExtension.activate",
    "onLanguage:javascript",
    "onFileSystem:sftp",
    "workspaceContains:**/.eslintrc.*",
    "*"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [],
    "menus": {},
    "keybindings": [],
    "languages": [],
    "grammars": [],
    "themes": [],
    "snippets": [],
    "configuration": {},
    "views": {},
    "viewsContainers": {},
    "problemMatchers": [],
    "debuggers": []
  },
  "scripts": {
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "package": "trae package",
    "publish": "trae publish"
  },
  "devDependencies": {
    "@types/trae": "^1.0.0",
    "typescript": "^4.0.0"
  }
}

开发环境设置

安装开发工具

bash
# 安装 Trae 扩展开发工具
npm install -g @trae/extension-generator
npm install -g @trae/extension-manager

# 创建新扩展
trae-ext create my-extension
cd my-extension

# 安装依赖
npm install

项目结构

my-extension/
├── .trae/
│   └── launch.json          # 调试配置
├── src/
│   ├── extension.ts         # 主扩展文件
│   ├── commands/
│   │   └── helloWorld.ts
│   ├── providers/
│   │   ├── completionProvider.ts
│   │   └── hoverProvider.ts
│   └── utils/
│       └── helpers.ts
├── syntaxes/
│   └── mylang.tmGrammar.json
├── themes/
│   └── my-theme.json
├── snippets/
│   └── javascript.json
├── images/
│   └── icon.png
├── CHANGELOG.md
├── README.md
├── package.json
└── tsconfig.json

TypeScript 配置

json
// tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2020",
    "outDir": "out",
    "lib": ["es2020"],
    "sourceMap": true,
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", ".trae-test"]
}

扩展 API

基本扩展结构

typescript
// src/extension.ts
import * as trae from 'trae';

export function activate(context: trae.ExtensionContext) {
    console.log('Extension "my-extension" is now active!');

    // 注册命令
    const disposable = trae.commands.registerCommand(
        'myExtension.helloWorld',
        () => {
            trae.window.showInformationMessage('Hello World from My Extension!');
        }
    );

    context.subscriptions.push(disposable);

    // 注册语言功能
    registerLanguageFeatures(context);
    
    // 注册视图
    registerViews(context);
}

export function deactivate() {
    console.log('Extension "my-extension" is now deactivated!');
}

function registerLanguageFeatures(context: trae.ExtensionContext) {
    // 代码补全提供者
    const completionProvider = trae.languages.registerCompletionItemProvider(
        'javascript',
        new MyCompletionProvider(),
        '.', '"', "'"
    );

    // 悬停提供者
    const hoverProvider = trae.languages.registerHoverProvider(
        'javascript',
        new MyHoverProvider()
    );

    context.subscriptions.push(completionProvider, hoverProvider);
}

function registerViews(context: trae.ExtensionContext) {
    // 自定义视图
    const provider = new MyTreeDataProvider();
    trae.window.createTreeView('myExtension.myView', {
        treeDataProvider: provider,
        showCollapseAll: true
    });
}

代码补全提供者

typescript
// src/providers/completionProvider.ts
import * as trae from 'trae';

export class MyCompletionProvider implements trae.CompletionItemProvider {
    provideCompletionItems(
        document: trae.TextDocument,
        position: trae.Position,
        token: trae.CancellationToken,
        context: trae.CompletionContext
    ): trae.ProviderResult<trae.CompletionItem[] | trae.CompletionList> {
        
        const linePrefix = document.lineAt(position).text.substr(0, position.character);
        
        if (!linePrefix.endsWith('console.')) {
            return undefined;
        }

        return [
            new trae.CompletionItem('log', trae.CompletionItemKind.Method),
            new trae.CompletionItem('warn', trae.CompletionItemKind.Method),
            new trae.CompletionItem('error', trae.CompletionItemKind.Method),
        ];
    }

    resolveCompletionItem(
        item: trae.CompletionItem,
        token: trae.CancellationToken
    ): trae.ProviderResult<trae.CompletionItem> {
        
        if (item.label === 'log') {
            item.detail = 'console.log()';
            item.documentation = new trae.MarkdownString('Outputs a message to the console.');
            item.insertText = new trae.SnippetString('log(${1:message})');
        }

        return item;
    }
}

悬停提供者

typescript
// src/providers/hoverProvider.ts
import * as trae from 'trae';

export class MyHoverProvider implements trae.HoverProvider {
    provideHover(
        document: trae.TextDocument,
        position: trae.Position,
        token: trae.CancellationToken
    ): trae.ProviderResult<trae.Hover> {
        
        const range = document.getWordRangeAtPosition(position);
        const word = document.getText(range);

        if (word === 'console') {
            const contents = new trae.MarkdownString();
            contents.appendCodeblock('console', 'javascript');
            contents.appendMarkdown('The **console** object provides access to the browser\'s debugging console.');
            
            return new trae.Hover(contents, range);
        }

        return undefined;
    }
}

树视图提供者

typescript
// src/providers/treeDataProvider.ts
import * as trae from 'trae';

export class MyTreeDataProvider implements trae.TreeDataProvider<TreeItem> {
    private _onDidChangeTreeData: trae.EventEmitter<TreeItem | undefined | null | void> = new trae.EventEmitter<TreeItem | undefined | null | void>();
    readonly onDidChangeTreeData: trae.Event<TreeItem | undefined | null | void> = this._onDidChangeTreeData.event;

    constructor() {}

    refresh(): void {
        this._onDidChangeTreeData.fire();
    }

    getTreeItem(element: TreeItem): trae.TreeItem {
        return element;
    }

    getChildren(element?: TreeItem): Thenable<TreeItem[]> {
        if (!element) {
            return Promise.resolve([
                new TreeItem('Item 1', trae.TreeItemCollapsibleState.None),
                new TreeItem('Item 2', trae.TreeItemCollapsibleState.Collapsed),
                new TreeItem('Item 3', trae.TreeItemCollapsibleState.None)
            ]);
        }
        
        return Promise.resolve([]);
    }
}

class TreeItem extends trae.TreeItem {
    constructor(
        public readonly label: string,
        public readonly collapsibleState: trae.TreeItemCollapsibleState
    ) {
        super(label, collapsibleState);
        this.tooltip = `${this.label} - Custom tooltip`;
        this.description = 'Description';
    }

    iconPath = {
        light: trae.Uri.joinPath(trae.extensions.getExtension('publisher.extension-name')!.extensionUri, 'images', 'light', 'icon.svg'),
        dark: trae.Uri.joinPath(trae.extensions.getExtension('publisher.extension-name')!.extensionUri, 'images', 'dark', 'icon.svg')
    };

    contextValue = 'treeItem';
}

语言支持

语法高亮

json
// syntaxes/mylang.tmGrammar.json
{
    "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
    "name": "MyLang",
    "patterns": [
        {
            "include": "#keywords"
        },
        {
            "include": "#strings"
        },
        {
            "include": "#comments"
        }
    ],
    "repository": {
        "keywords": {
            "patterns": [{
                "name": "keyword.control.mylang",
                "match": "\\b(if|while|for|return|function)\\b"
            }]
        },
        "strings": {
            "name": "string.quoted.double.mylang",
            "begin": "\"",
            "end": "\"",
            "patterns": [
                {
                    "name": "constant.character.escape.mylang",
                    "match": "\\\\."
                }
            ]
        },
        "comments": {
            "patterns": [
                {
                    "name": "comment.line.double-slash.mylang",
                    "match": "//.*$"
                },
                {
                    "name": "comment.block.mylang",
                    "begin": "/\\*",
                    "end": "\\*/"
                }
            ]
        }
    },
    "scopeName": "source.mylang"
}

语言配置

json
// language-configuration.json
{
    "comments": {
        "lineComment": "//",
        "blockComment": ["/*", "*/"]
    },
    "brackets": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"]
    ],
    "autoClosingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ],
    "surroundingPairs": [
        ["{", "}"],
        ["[", "]"],
        ["(", ")"],
        ["\"", "\""],
        ["'", "'"]
    ],
    "folding": {
        "markers": {
            "start": "^\\s*//\\s*#region\\b",
            "end": "^\\s*//\\s*#endregion\\b"
        }
    },
    "wordPattern": "[a-zA-Z_$][a-zA-Z0-9_$]*",
    "indentationRules": {
        "increaseIndentPattern": "^.*\\{[^}\"']*$",
        "decreaseIndentPattern": "^\\s*\\}"
    }
}

代码片段

json
// snippets/javascript.json
{
    "Print to console": {
        "prefix": "log",
        "body": [
            "console.log('$1');",
            "$2"
        ],
        "description": "Log output to console"
    },
    "Function": {
        "prefix": "func",
        "body": [
            "function ${1:name}(${2:params}) {",
            "\t$3",
            "}"
        ],
        "description": "Create a function"
    },
    "Arrow Function": {
        "prefix": "arrow",
        "body": [
            "const ${1:name} = (${2:params}) => {",
            "\t$3",
            "};"
        ],
        "description": "Create an arrow function"
    }
}

调试器扩展

调试器配置

json
// package.json - debuggers contribution
{
  "contributes": {
    "debuggers": [{
      "type": "mylang",
      "label": "MyLang Debug",
      "program": "./out/debugAdapter.js",
      "runtime": "node",
      "configurationAttributes": {
        "launch": {
          "required": ["program"],
          "properties": {
            "program": {
              "type": "string",
              "description": "Absolute path to a text file.",
              "default": "${workspaceFolder}/${command:AskForProgramName}"
            },
            "stopOnEntry": {
              "type": "boolean",
              "description": "Automatically stop after launch.",
              "default": true
            }
          }
        }
      },
      "initialConfigurations": [
        {
          "type": "mylang",
          "request": "launch",
          "name": "Ask for file name",
          "program": "${workspaceFolder}/${command:AskForProgramName}",
          "stopOnEntry": true
        }
      ],
      "configurationSnippets": [
        {
          "label": "MyLang Debug: Launch",
          "description": "A new configuration for 'debugging' a user selected mylang file.",
          "body": {
            "type": "mylang",
            "request": "launch",
            "name": "Launch Program",
            "program": "${workspaceFolder}/${command:AskForProgramName}"
          }
        }
      ]
    }]
  }
}

调试适配器

typescript
// src/debugAdapter.ts
import {
    DebugSession,
    InitializedEvent,
    TerminatedEvent,
    StoppedEvent,
    BreakpointEvent,
    OutputEvent,
    Thread,
    StackFrame,
    Scope,
    Source,
    Handles,
    Breakpoint
} from 'trae-debugadapter';
import { DebugProtocol } from 'trae-debugprotocol';
import { basename } from 'path';

export class MyLangDebugSession extends DebugSession {
    private static THREAD_ID = 1;
    private _variableHandles = new Handles<string>();
    private _configurationDone = new Subject();

    public constructor() {
        super();
        this.setDebuggerLinesStartAt1(false);
        this.setDebuggerColumnsStartAt1(false);
    }

    protected initializeRequest(
        response: DebugProtocol.InitializeResponse,
        args: DebugProtocol.InitializeRequestArguments
    ): void {
        response.body = response.body || {};
        response.body.supportsConfigurationDoneRequest = true;
        response.body.supportsEvaluateForHovers = true;
        response.body.supportsStepBack = false;
        response.body.supportsDataBreakpoints = false;
        response.body.supportsCompletionsRequest = true;
        response.body.completionTriggerCharacters = [".", "["];
        response.body.supportsCancelRequest = false;
        response.body.supportsBreakpointLocationsRequest = true;
        
        this.sendResponse(response);
        this.sendEvent(new InitializedEvent());
    }

    protected configurationDoneRequest(
        response: DebugProtocol.ConfigurationDoneResponse,
        args: DebugProtocol.ConfigurationDoneArguments
    ): void {
        super.configurationDoneRequest(response, args);
        this._configurationDone.notify();
    }

    protected async launchRequest(
        response: DebugProtocol.LaunchResponse,
        args: ILaunchRequestArguments
    ) {
        await this._configurationDone.wait(1000);
        
        // 启动调试会话
        this.sendResponse(response);
        
        if (args.stopOnEntry) {
            this.sendEvent(new StoppedEvent('entry', MyLangDebugSession.THREAD_ID));
        } else {
            this.continueRequest(<DebugProtocol.ContinueResponse>response, { threadId: MyLangDebugSession.THREAD_ID });
        }
    }

    protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
        response.body = {
            threads: [
                new Thread(MyLangDebugSession.THREAD_ID, "thread 1")
            ]
        };
        this.sendResponse(response);
    }

    protected stackTraceRequest(
        response: DebugProtocol.StackTraceResponse,
        args: DebugProtocol.StackTraceArguments
    ): void {
        const frames = new Array<StackFrame>();
        
        // 创建堆栈帧
        const frame = new StackFrame(
            0,
            'main',
            this.createSource('main.mylang'),
            1,
            0
        );
        frames.push(frame);

        response.body = {
            stackFrames: frames,
            totalFrames: frames.length
        };
        this.sendResponse(response);
    }

    private createSource(filePath: string): Source {
        return new Source(
            basename(filePath),
            this.convertDebuggerPathToClient(filePath),
            undefined,
            undefined,
            'mylang-adapter-data'
        );
    }
}

interface ILaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
    program: string;
    stopOnEntry?: boolean;
}

主题开发

主题文件结构

json
// themes/my-theme.json
{
    "name": "My Beautiful Theme",
    "type": "dark",
    "colors": {
        // 编辑器颜色
        "editor.background": "#1e1e1e",
        "editor.foreground": "#d4d4d4",
        "editor.lineHighlightBackground": "#2d2d30",
        "editor.selectionBackground": "#264f78",
        "editor.selectionHighlightBackground": "#add6ff26",
        "editor.wordHighlightBackground": "#575757b8",
        "editor.wordHighlightStrongBackground": "#004972b8",
        "editor.findMatchBackground": "#515c6a",
        "editor.findMatchHighlightBackground": "#ea5c0055",
        "editor.hoverHighlightBackground": "#264f7840",
        "editor.linkActiveForeground": "#4e94ce",
        "editor.rangeHighlightBackground": "#ffffff0b",
        
        // 活动栏
        "activityBar.background": "#2d2d30",
        "activityBar.foreground": "#ffffff",
        "activityBar.inactiveForeground": "#ffffff66",
        "activityBar.border": "#2d2d30",
        "activityBarBadge.background": "#007acc",
        "activityBarBadge.foreground": "#ffffff",
        
        // 侧边栏
        "sideBar.background": "#252526",
        "sideBar.foreground": "#cccccc",
        "sideBar.border": "#2d2d30",
        "sideBarTitle.foreground": "#cccccc",
        "sideBarSectionHeader.background": "#2d2d30",
        "sideBarSectionHeader.foreground": "#cccccc",
        
        // 状态栏
        "statusBar.background": "#007acc",
        "statusBar.foreground": "#ffffff",
        "statusBar.border": "#007acc",
        "statusBar.debuggingBackground": "#cc6633",
        "statusBar.debuggingForeground": "#ffffff",
        "statusBar.noFolderBackground": "#68217a",
        "statusBar.noFolderForeground": "#ffffff",
        
        // 标签页
        "tab.activeBackground": "#1e1e1e",
        "tab.activeForeground": "#ffffff",
        "tab.border": "#2d2d30",
        "tab.activeBorder": "#007acc",
        "tab.unfocusedActiveBorder": "#2d2d30",
        "tab.inactiveBackground": "#2d2d30",
        "tab.inactiveForeground": "#ffffff80",
        "tab.unfocusedActiveForeground": "#ffffff80",
        "tab.unfocusedInactiveForeground": "#ffffff40",
        
        // 终端
        "terminal.background": "#1e1e1e",
        "terminal.foreground": "#d4d4d4",
        "terminal.ansiBlack": "#000000",
        "terminal.ansiRed": "#cd3131",
        "terminal.ansiGreen": "#0dbc79",
        "terminal.ansiYellow": "#e5e510",
        "terminal.ansiBlue": "#2472c8",
        "terminal.ansiMagenta": "#bc3fbc",
        "terminal.ansiCyan": "#11a8cd",
        "terminal.ansiWhite": "#e5e5e5"
    },
    "tokenColors": [
        {
            "name": "Comment",
            "scope": [
                "comment",
                "punctuation.definition.comment"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#6A9955"
            }
        },
        {
            "name": "Variables",
            "scope": [
                "variable",
                "string constant.other.placeholder"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "Colors",
            "scope": [
                "constant.other.color"
            ],
            "settings": {
                "foreground": "#ffffff"
            }
        },
        {
            "name": "Invalid",
            "scope": [
                "invalid",
                "invalid.illegal"
            ],
            "settings": {
                "foreground": "#f44747"
            }
        },
        {
            "name": "Keyword, Storage",
            "scope": [
                "keyword",
                "storage.type",
                "storage.modifier"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Operator, Misc",
            "scope": [
                "keyword.control",
                "constant.other.color",
                "punctuation",
                "meta.tag",
                "punctuation.definition.tag",
                "punctuation.separator.inheritance.php",
                "punctuation.definition.tag.html",
                "punctuation.definition.tag.begin.html",
                "punctuation.definition.tag.end.html",
                "punctuation.section.embedded",
                "keyword.other.template",
                "keyword.other.substitution"
            ],
            "settings": {
                "foreground": "#D4D4D4"
            }
        },
        {
            "name": "Tag",
            "scope": [
                "entity.name.tag",
                "meta.tag.sgml",
                "markup.deleted.git_gutter"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Function, Special Method",
            "scope": [
                "entity.name.function",
                "meta.function-call",
                "variable.function",
                "support.function",
                "keyword.other.special-method"
            ],
            "settings": {
                "foreground": "#DCDCAA"
            }
        },
        {
            "name": "Block Level Variables",
            "scope": [
                "meta.block variable.other"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "Other Variable, String Link",
            "scope": [
                "support.other.variable",
                "string.other.link"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "Number, Constant, Function Argument, Tag Attribute, Embedded",
            "scope": [
                "constant.numeric",
                "constant.language",
                "support.constant",
                "constant.character",
                "constant.escape",
                "variable.parameter",
                "keyword.other.unit",
                "keyword.other"
            ],
            "settings": {
                "foreground": "#B5CEA8"
            }
        },
        {
            "name": "String, Symbols, Inherited Class, Markup Heading",
            "scope": [
                "string",
                "constant.other.symbol",
                "constant.other.key",
                "entity.other.inherited-class",
                "markup.heading",
                "markup.inserted.git_gutter",
                "meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js"
            ],
            "settings": {
                "foreground": "#CE9178"
            }
        },
        {
            "name": "Class, Support",
            "scope": [
                "entity.name",
                "support.type",
                "support.class",
                "support.other.namespace.use.php",
                "meta.use.php",
                "support.other.namespace.php",
                "markup.changed.git_gutter",
                "support.type.sys-types"
            ],
            "settings": {
                "foreground": "#4EC9B0"
            }
        },
        {
            "name": "Entity Types",
            "scope": [
                "support.type"
            ],
            "settings": {
                "foreground": "#4EC9B0"
            }
        },
        {
            "name": "CSS Class and Support",
            "scope": [
                "source.css support.type.property-name",
                "source.sass support.type.property-name",
                "source.scss support.type.property-name",
                "source.less support.type.property-name",
                "source.stylus support.type.property-name",
                "source.postcss support.type.property-name"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "Sub-methods",
            "scope": [
                "entity.name.module.js",
                "variable.import.parameter.js",
                "variable.other.class.js"
            ],
            "settings": {
                "foreground": "#FF8C00"
            }
        },
        {
            "name": "Language methods",
            "scope": [
                "variable.language"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#569CD6"
            }
        },
        {
            "name": "entity.name.method.js",
            "scope": [
                "entity.name.method.js"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#DCDCAA"
            }
        },
        {
            "name": "meta.method.js",
            "scope": [
                "meta.class-method.js entity.name.function.js",
                "variable.function.constructor"
            ],
            "settings": {
                "foreground": "#DCDCAA"
            }
        },
        {
            "name": "Attributes",
            "scope": [
                "entity.other.attribute-name"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "HTML Attributes",
            "scope": [
                "text.html.basic entity.other.attribute-name.html",
                "text.html.basic entity.other.attribute-name"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "CSS Classes",
            "scope": [
                "entity.other.attribute-name.class"
            ],
            "settings": {
                "foreground": "#FFCB6B"
            }
        },
        {
            "name": "CSS ID's",
            "scope": [
                "source.sass keyword.control"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Inserted",
            "scope": [
                "markup.inserted"
            ],
            "settings": {
                "foreground": "#B5CEA8"
            }
        },
        {
            "name": "Deleted",
            "scope": [
                "markup.deleted"
            ],
            "settings": {
                "foreground": "#CE9178"
            }
        },
        {
            "name": "Changed",
            "scope": [
                "markup.changed"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Regular Expressions",
            "scope": [
                "string.regexp"
            ],
            "settings": {
                "foreground": "#D16969"
            }
        },
        {
            "name": "Escape Characters",
            "scope": [
                "constant.character.escape"
            ],
            "settings": {
                "foreground": "#D7BA7D"
            }
        },
        {
            "name": "URL",
            "scope": [
                "*url*",
                "*link*",
                "*uri*"
            ],
            "settings": {
                "fontStyle": "underline"
            }
        },
        {
            "name": "Decorators",
            "scope": [
                "tag.decorator.js entity.name.tag.js",
                "tag.decorator.js punctuation.definition.tag.js"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#4EC9B0"
            }
        },
        {
            "name": "ES7 Bind Operator",
            "scope": [
                "source.js constant.other.object.key.js string.unquoted.label.js"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#FF8C00"
            }
        },
        {
            "name": "JSON Key - Level 0",
            "scope": [
                "source.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#9CDCFE"
            }
        },
        {
            "name": "JSON Key - Level 1",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#FFCB6B"
            }
        },
        {
            "name": "JSON Key - Level 2",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#F78C6C"
            }
        },
        {
            "name": "JSON Key - Level 3",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#FF5370"
            }
        },
        {
            "name": "JSON Key - Level 4",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#C17E70"
            }
        },
        {
            "name": "JSON Key - Level 5",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#82AAFF"
            }
        },
        {
            "name": "JSON Key - Level 6",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#f07178"
            }
        },
        {
            "name": "JSON Key - Level 7",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#C792EA"
            }
        },
        {
            "name": "JSON Key - Level 8",
            "scope": [
                "source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json"
            ],
            "settings": {
                "foreground": "#C3E88D"
            }
        },
        {
            "name": "Markdown - Plain",
            "scope": [
                "text.html.markdown",
                "punctuation.definition.list_item.markdown"
            ],
            "settings": {
                "foreground": "#CCCCCC"
            }
        },
        {
            "name": "Markdown - Markup Raw Inline",
            "scope": [
                "text.html.markdown markup.inline.raw.markdown"
            ],
            "settings": {
                "foreground": "#CE9178"
            }
        },
        {
            "name": "Markdown - Markup Raw Inline Punctuation",
            "scope": [
                "text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown"
            ],
            "settings": {
                "foreground": "#D4D4D4"
            }
        },
        {
            "name": "Markdown - Heading",
            "scope": [
                "markdown.heading",
                "markup.heading | markup.heading entity.name",
                "markup.heading.markdown punctuation.definition.heading.markdown"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Markup - Italic",
            "scope": [
                "markup.italic"
            ],
            "settings": {
                "fontStyle": "italic",
                "foreground": "#f07178"
            }
        },
        {
            "name": "Markup - Bold",
            "scope": [
                "markup.bold",
                "markup.bold string"
            ],
            "settings": {
                "fontStyle": "bold",
                "foreground": "#f07178"
            }
        },
        {
            "name": "Markup - Bold-Italic",
            "scope": [
                "markup.bold markup.italic",
                "markup.italic markup.bold",
                "markup.quote markup.bold",
                "markup.bold markup.italic string",
                "markup.italic markup.bold string",
                "markup.quote markup.bold string"
            ],
            "settings": {
                "fontStyle": "bold italic",
                "foreground": "#f07178"
            }
        },
        {
            "name": "Markup - Underline",
            "scope": [
                "markup.underline"
            ],
            "settings": {
                "fontStyle": "underline",
                "foreground": "#F78C6C"
            }
        },
        {
            "name": "Markdown - Blockquote",
            "scope": [
                "markup.quote punctuation.definition.blockquote.markdown"
            ],
            "settings": {
                "foreground": "#FFCB6B"
            }
        },
        {
            "name": "Markup - Quote",
            "scope": [
                "markup.quote"
            ],
            "settings": {
                "fontStyle": "italic"
            }
        },
        {
            "name": "Markdown - Link",
            "scope": [
                "string.other.link.title.markdown"
            ],
            "settings": {
                "foreground": "#DCDCAA"
            }
        },
        {
            "name": "Markdown - Link Description",
            "scope": [
                "string.other.link.description.title.markdown"
            ],
            "settings": {
                "foreground": "#CE9178"
            }
        },
        {
            "name": "Markdown - Link Anchor",
            "scope": [
                "constant.other.reference.link.markdown"
            ],
            "settings": {
                "foreground": "#FFCB6B"
            }
        },
        {
            "name": "Markup - Raw Block",
            "scope": [
                "markup.raw.block"
            ],
            "settings": {
                "foreground": "#CE9178"
            }
        },
        {
            "name": "Markdown - Raw Block Fenced",
            "scope": [
                "markup.raw.block.fenced.markdown"
            ],
            "settings": {
                "foreground": "#00000050"
            }
        },
        {
            "name": "Markdown - Fenced Bode Block",
            "scope": [
                "punctuation.definition.fenced.markdown"
            ],
            "settings": {
                "foreground": "#00000050"
            }
        },
        {
            "name": "Markdown - Fenced Bode Block Variable",
            "scope": [
                "markup.raw.block.fenced.markdown",
                "variable.language.fenced.markdown",
                "punctuation.section.class.end"
            ],
            "settings": {
                "foreground": "#CCCCCC"
            }
        },
        {
            "name": "Markdown - Fenced Language",
            "scope": [
                "variable.language.fenced.markdown"
            ],
            "settings": {
                "foreground": "#569CD6"
            }
        },
        {
            "name": "Markdown - Separator",
            "scope": [
                "meta.separator"
            ],
            "settings": {
                "fontStyle": "bold",
                "foreground": "#2e3440"
            }
        },
        {
            "name": "Markup - Table",
            "scope": [
                "markup.table"
            ],
            "settings": {
                "foreground": "#CCCCCC"
            }
        }
    ]
}

测试和调试

扩展测试

typescript
// src/test/suite/extension.test.ts
import * as assert from 'assert';
import * as trae from 'trae';
import * as myExtension from '../../extension';

suite('Extension Test Suite', () => {
    trae.window.showInformationMessage('Start all tests.');

    test('Extension should be present', () => {
        assert.ok(trae.extensions.getExtension('publisher.my-extension'));
    });

    test('Extension should activate', async () => {
        const ext = trae.extensions.getExtension('publisher.my-extension')!;
        await ext.activate();
        assert.strictEqual(ext.isActive, true);
    });

    test('Should register hello world command', async () => {
        const commands = await trae.commands.getCommands(true);
        assert.ok(commands.includes('myExtension.helloWorld'));
    });

    test('Hello world command should show message', async () => {
        // 模拟命令执行
        await trae.commands.executeCommand('myExtension.helloWorld');
        // 验证消息显示(需要模拟 showInformationMessage)
    });
});

调试配置

json
// .trae/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}"
            ],
            "outFiles": [
                "${workspaceFolder}/out/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: watch"
        },
        {
            "name": "Extension Tests",
            "type": "extensionHost",
            "request": "launch",
            "args": [
                "--extensionDevelopmentPath=${workspaceFolder}",
                "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
            ],
            "outFiles": [
                "${workspaceFolder}/out/test/**/*.js"
            ],
            "preLaunchTask": "${workspaceFolder}/npm: watch"
        }
    ]
}

任务配置

json
// .trae/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "npm",
            "script": "watch",
            "problemMatcher": "$tsc-watch",
            "isBackground": true,
            "presentation": {
                "reveal": "never"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "type": "npm",
            "script": "compile",
            "problemMatcher": "$tsc",
            "group": "build"
        },
        {
            "label": "package extension",
            "type": "shell",
            "command": "trae package",
            "group": "build",
            "dependsOn": "npm: compile"
        }
    ]
}

打包和发布

打包扩展

bash
# 安装打包工具
npm install -g @trae/trae-cli

# 编译 TypeScript
npm run compile

# 打包扩展
trae package

# 这将创建 .trex 文件
# my-extension-1.0.0.trex

发布到市场

bash
# 登录到 Trae 市场
trae login

# 发布扩展
trae publish

# 或者发布特定版本
trae publish my-extension-1.0.0.trex

# 更新扩展
trae publish --update

版本管理

bash
# 增加版本号
npm version patch  # 1.0.0 -> 1.0.1
npm version minor  # 1.0.1 -> 1.1.0
npm version major  # 1.1.0 -> 2.0.0

# 发布新版本
trae publish

发布配置

json
// .traerc.json
{
  "publisher": "your-publisher-name",
  "registry": "https://marketplace.trae.dev",
  "token": "your-access-token",
  "categories": [
    "Programming Languages",
    "Themes",
    "Debuggers"
  ],
  "badges": [
    {
      "url": "https://img.shields.io/badge/build-passing-brightgreen",
      "href": "https://github.com/user/repo/actions",
      "description": "Build Status"
    }
  ],
  "qna": "marketplace",
  "sponsor": {
    "url": "https://github.com/sponsors/username"
  }
}

最佳实践

代码质量

  1. 使用 TypeScript:提供类型安全和更好的开发体验
  2. 遵循命名约定:使用清晰的命名和一致的代码风格
  3. 错误处理:妥善处理异常和边界情况
  4. 性能优化:避免阻塞主线程,使用异步操作

用户体验

  1. 清晰的文档:提供详细的 README 和使用说明
  2. 合理的默认值:设置合理的默认配置
  3. 渐进式功能:允许用户逐步启用功能
  4. 反馈机制:提供清晰的状态反馈和错误消息

兼容性

  1. 版本兼容:支持多个 Trae 版本
  2. 平台兼容:确保在不同操作系统上正常工作
  3. 向后兼容:保持 API 的向后兼容性
  4. 依赖管理:最小化外部依赖

安全性

  1. 输入验证:验证所有用户输入
  2. 权限最小化:只请求必要的权限
  3. 安全存储:安全存储敏感信息
  4. 代码审查:定期进行安全代码审查

故障排除

常见问题

扩展无法激活

typescript
// 检查激活事件
"activationEvents": [
    "onCommand:myExtension.activate",
    "onLanguage:javascript"
]

// 确保主文件路径正确
"main": "./out/extension.js"

// 检查编译输出
npm run compile

命令未注册

typescript
// 确保在 package.json 中声明命令
"contributes": {
    "commands": [{
        "command": "myExtension.helloWorld",
        "title": "Hello World"
    }]
}

// 在扩展代码中注册命令
const disposable = trae.commands.registerCommand(
    'myExtension.helloWorld',
    () => {
        trae.window.showInformationMessage('Hello World!');
    }
);
context.subscriptions.push(disposable);

语言功能不工作

typescript
// 检查语言 ID 是否正确
"languages": [{
    "id": "mylang",
    "aliases": ["MyLang", "mylang"],
    "extensions": [".ml", ".mylang"]
}]

// 确保提供者正确注册
const provider = trae.languages.registerCompletionItemProvider(
    'mylang', // 语言 ID 必须匹配
    new MyCompletionProvider()
);

调试技巧

使用开发者工具

typescript
// 在扩展代码中添加调试信息
console.log('Extension activated');
console.error('Error occurred:', error);

// 使用 Trae 的输出面板
const outputChannel = trae.window.createOutputChannel('My Extension');
outputChannel.appendLine('Debug message');
outputChannel.show();

性能分析

typescript
// 测量执行时间
const start = Date.now();
// 执行代码
const end = Date.now();
console.log(`Execution time: ${end - start}ms`);

// 使用性能标记
performance.mark('start-operation');
// 执行代码
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');

高级主题

自定义编辑器

typescript
// 注册自定义编辑器
export class MyCustomEditorProvider implements trae.CustomTextEditorProvider {
    public static register(context: trae.ExtensionContext): trae.Disposable {
        const provider = new MyCustomEditorProvider(context);
        const providerRegistration = trae.window.registerCustomEditorProvider(
            MyCustomEditorProvider.viewType,
            provider
        );
        return providerRegistration;
    }

    private static readonly viewType = 'myExtension.customEditor';

    constructor(private readonly context: trae.ExtensionContext) {}

    public async resolveCustomTextEditor(
        document: trae.TextDocument,
        webviewPanel: trae.WebviewPanel,
        _token: trae.CancellationToken
    ): Promise<void> {
        webviewPanel.webview.options = {
            enableScripts: true,
        };
        webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);

        function updateWebview() {
            webviewPanel.webview.postMessage({
                type: 'update',
                text: document.getText(),
            });
        }

        const changeDocumentSubscription = trae.workspace.onDidChangeTextDocument(e => {
            if (e.document.uri.toString() === document.uri.toString()) {
                updateWebview();
            }
        });

        webviewPanel.onDidDispose(() => {
            changeDocumentSubscription.dispose();
        });

        webviewPanel.webview.onDidReceiveMessage(e => {
            switch (e.type) {
                case 'add':
                    this.addNewScratch(document);
                    return;
                case 'delete':
                    this.deleteScratch(document, e.id);
                    return;
            }
        });

        updateWebview();
    }

    private getHtmlForWebview(webview: trae.Webview): string {
        return `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Editor</title>
</head>
<body>
    <div id="app"></div>
    <script>
        const trae = acquireTraeApi();
        // 自定义编辑器逻辑
    </script>
</body>
</html>`;
    }
}

文件系统提供者

typescript
export class MyFileSystemProvider implements trae.FileSystemProvider {
    private _emitter = new trae.EventEmitter<trae.FileChangeEvent[]>();
    readonly onDidChangeFile: trae.Event<trae.FileChangeEvent[]> = this._emitter.event;

    watch(uri: trae.Uri, options: { recursive: boolean; excludes: string[]; }): trae.Disposable {
        // 实现文件监视逻辑
        return new trae.Disposable(() => {});
    }

    stat(uri: trae.Uri): trae.FileStat | Thenable<trae.FileStat> {
        // 返回文件统计信息
        return {
            type: trae.FileType.File,
            ctime: Date.now(),
            mtime: Date.now(),
            size: 0
        };
    }

    readDirectory(uri: trae.Uri): [string, trae.FileType][] | Thenable<[string, trae.FileType][]> {
        // 读取目录内容
        return [];
    }

    createDirectory(uri: trae.Uri): void | Thenable<void> {
        // 创建目录
    }

    readFile(uri: trae.Uri): Uint8Array | Thenable<Uint8Array> {
        // 读取文件内容
        return new Uint8Array();
    }

    writeFile(uri: trae.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; }): void | Thenable<void> {
        // 写入文件内容
    }

    delete(uri: trae.Uri, options: { recursive: boolean; }): void | Thenable<void> {
        // 删除文件或目录
    }

    rename(oldUri: trae.Uri, newUri: trae.Uri, options: { overwrite: boolean; }): void | Thenable<void> {
        // 重命名文件或目录
    }
}

// 注册文件系统提供者
const provider = new MyFileSystemProvider();
context.subscriptions.push(
    trae.workspace.registerFileSystemProvider('myscheme', provider, {
        isCaseSensitive: true
    })
);

社区和资源

官方资源

社区资源

学习资源


通过本指南,您应该能够开始开发自己的 Trae IDE 扩展。记住要遵循最佳实践,编写高质量的代码,并为用户提供良好的体验。如果遇到问题,不要犹豫向社区寻求帮助!

您的终极 AI 驱动 IDE 学习指南