nkds

导航

 

MonkeyCode 自定义主题与扩展系统:打造专属于你的 AI 编程环境

引言

"每个开发者都是独一无二的——你的工具应该适应你,而不是让你去适应工具。"

在 AI 编程助手领域,"一刀切"的体验早已无法满足开发者的多样化需求。有的开发者喜欢深色背景护眼,有的偏爱明亮界面提神;有的需要极简界面专注编码,有的希望信息密度最大化;有的团队有严格的代码规范,有的个人开发者追求极致自由。

MonkeyCode 作为完全开源(Apache License 2.0)的 AI 编程助手,从设计之初就将高度可定制性作为核心架构原则。本文将深入剖析 MonkeyCode 的主题系统、扩展机制、以及如何让每一位开发者都能打造出最适合自己的 AI 编程环境。

🎯 核心信息

  • GitHub 仓库: https://github.com/monkeycode-ai/monkeycode
  • 开源协议: Apache License 2.0
  • 主题系统版本: v4.1 (支持 CSS 变量 + JSON Schema)
  • 扩展 API: 完整的 Plugin SDK + Webhook 系统
  • 欢迎贡献主题和扩展!

一、主题系统架构

1.1 主题架构总览

┌─────────────────────────────────────────────────────────────┐
│              MonkeyCode 主题系统架构                           │
│                                                             │
│  ══════════════════════════════════════════════════════    │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              主题定义层 (Theme Definition)            │   │
│  │                                                     │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐           │   │
│  │  │ 内置主题  │ │ 社区主题  │ │ 自定义   │           │   │
│  │  │ (8款)   │ │ (50+款) │ │ 用户主题 │           │   │
│  │  └──────────┘ └──────────┘ └──────────┘           │   │
│  └─────────────────────┬───────────────────────────────┘   │
│                        │                                    │
│  ┌─────────────────────▼───────────────────────────────┐   │
│  │             主题引擎层 (Theme Engine)                 │   │
│  │                                                     │   │
│  │  • CSS Variables Resolver                           │   │
│  │  • Color Palette Generator                          │   │
│  │  • Syntax Highlighting Mapper                       │   │
│  │  • Icon Set Manager                                 │   │
│  │  • Font Configuration                               │   │
│  │  • Layout Template Engine                           │   │
│  └─────────────────────┬───────────────────────────────┘   │
│                        │                                    │
│  ┌─────────────────────▼───────────────────────────────┐   │
│  │              渲染层 (Rendering)                      │   │
│  │                                                     │   │
│  │  ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐     │   │
│  │  │编辑器  │ │面板    │ │状态栏  │ │通知   │     │   │
│  │  │主题    │ │主题    │ │主题    │ │主题   │     │   │
│  │  └────────┘ └────────┘ └────────┘ └────────┘     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
╚═══════════════════════════════════════════════════════════╝

1.2 主题 Schema 定义

// ===== packages/theme/src/schema/theme-schema.ts =====
/**
 * MonkeyCode 主题 Schema 定义
 * 
 * 使用 JSON Schema 定义完整的主题配置,
 * 支持类型检查、自动补全、验证和迁移
 */

import { z } from 'zod';

// === 颜色定义 ===
export const ColorTokenSchema = z.object({
  // 核心颜色
  primary: z.string(),                // 主色调
  secondary: z.string(),              // 辅助色
  accent: z.string(),                 // 强调色
  
  // 背景色
  background: z.object({
    primary: z.string(),              // 主背景
    secondary: z.string(),            // 次级背景(侧边栏等)
    tertiary: z.string(),            // 三级背景(卡片、弹窗)
    elevated: z.string(),            // 浮起元素(按钮、输入框)
  }),
  
  // 前景色(文字)
  foreground: z.object({
    primary: z.string(),              // 主文字
    secondary: z.string(),            // 次要文字
    muted: z.string(),               // 弱化文字(占位符、提示)
    disabled: z.string(),            // 禁用文字
    link: z.string(),                // 链接颜色
  }),
  
  // 边框
  border: z.object({
    default: z.string(),              // 默认边框
    subtle: z.string(),              // 微妙边框
    focus: z.string(),              // 聚焦边框
    active: z.string(),             // 激活边框
  }),
  
  // 状态色
  status: z.object({
    success: z.string(),
    warning: z.string(),
    error: z.string(),
    info: z.string(),
  }),
  
  // 语法高亮(代码着色)
  syntax: z.object({
    comment: z.string(),
    string: z.string(),
    keyword: z.string(),
    function: z.string(),
    variable: z.string(),
    type: z.string(),
    number: z.string(),
    operator: z.string(),
    punctuation: z.string(),
    class: z.string(),
    constant: z.string(),
    decorator: z.string(),
    property: z.string(),
    parameter: z.string(),
    regexp: z.string(),
    tag: z.string(),
    attributeName: z.string(),
    attributeValue: z.string(),
  }),
});

// === 字体定义 ===
export const FontConfigSchema = z.object({
  // 主字体
  fontFamily: z.object({
    mono: z.string().default("'JetBrains Mono', 'Fira Code', Consolas, monospace"),
    sans: z.string().default("-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"),
    serif: z.string().default("'Georgia', 'Times New Roman', serif"),
  }),
  
  // 字号
  fontSize: z.object({
    xs: z.number().default(11),       // 极小
    sm: z.number().default(12),       // 小
    base: z.number().default(14),     // 基准
    lg: z.number().default(16),       // 大
    xl: z.number().default(18),       // 特大
    '2xl': z.number().default(24),    // 标题
    '3xl': z.number().default(30),    // 大标题
  }),
  
  // 行高
  lineHeight: z.object({
    tight: z.number().default(1.25),
    normal: z.number().default(1.5),
    relaxed: z.number().default(1.75),
  }),
  
  // 字重
  fontWeight: z.object({
    normal: z.number().default(400),
    medium: z.number().default(500),
    semibold: z.number().default(600),
    bold: z.number().default(700),
  }),
  
  // 字间距
  letterSpacing: z.object({
    tight: z.string().default('-0.02em'),
    normal: z.string().default('0'),
    wide: z.string().default('0.05em'),
  }),
  
  // 特性
  features: z.array(z.object({
    feature: z.string(),              // 如 "liga", "calt"
    setting: z.string(),             // 如 "on", "off"
  })).optional(),
});

// === 间距系统 ===
export const SpacingSchema = z.object({
  unit: z.number().default(4),         // 基础单位 (px)
  scale: z.object({
    '0': z.number().default(0),
    '1': z.number().default(4),
    '2': z.number().default(8),
    '3': z.number().default(12),
    '4': z.number().default(16),
    '5': z.number().default(20),
    '6': z.number().default(24),
    '8': z.number().default(32),
    '10': z.number().default(40),
    '12': z.number().default(48),
    '16': z.number().default(64),
  }),
  
  borderRadius: z.object({
    none: z.string().default('0'),
    sm: z.string().default('4px'),
    md: z.string().default('8px'),
    lg: z.string().default('12px'),
    xl: z.string().default('16px'),
    full: z.string().default('9999px'),
  }),
});

// === 完整主题 Schema ===
export const ThemeSchema = z.object({
  // 元数据
  metadata: z.object({
    name: z.string(),
    version: z.string(),
    author: z.string(),
    description: z.string().optional(),
    displayName: z.string(),
    
    // 兼容性
    engineVersion: z.string().default('^4.0'),
    
    // 分类标签
    tags: z.array(z.enum([
      'dark', 'light', 'high-contrast',
      'minimal', 'colorful', 'pastel',
      'retro', 'futuristic', 'nature',
      'professional', 'playful',
    ])).optional(),
  }),
  
  // 类型
  type: z.enum(['dark', 'light']).default('dark'),
  
  // 颜色
  colors: ColorTokenSchema,
  
  // 字体
  fonts: FontConfigSchema,
  
  // 间距
  spacing: SpacingSchema,
  
  // UI 组件覆盖
  components: z.record(z.any()).optional(),
  
  // 自定义 CSS
  customCSS: z.string().optional(),
  
  // 继承关系
  extends: z.string().optional(),      // 继承自哪个主题
});

export type Theme = z.infer<typeof ThemeSchema>;
export type ColorTokens = z.infer<typeof ColorTokenSchema>;

1.3 内置主题展示

// ===== packages/theme/src/built-in-themes.ts =====
/**
 * MonkeyCode 内置主题集合
 * 
 * 提供 8 款精心设计的开箱即用主题
 */

import { Theme } from './schema';

export const builtInThemes: Record<string, Theme> = {
  // === 深色主题 ===
  
  'midnight-blue': {
    metadata: {
      name: 'midnight-blue',
      version: '4.1.0',
      author: 'MonkeyCode Team',
      displayName: '🌙 午夜蓝',
      description: '经典的深蓝色调,适合长时间编程',
      tags: ['dark', 'professional'],
    },
    type: 'dark',
    colors: {
      primary: '#6C9BD3',
      secondary: '#7B9FCF',
      accent: '#5B8DD1',
      background: {
        primary: '#0D1117',
        secondary: '#161B22',
        tertiary: '#21262D',
        elevated: '#30363D',
      },
      foreground: {
        primary: '#E6EDF3',
        secondary: '#B1BCC8',
        muted: '#656D76',
        disabled: '#484F58',
        link: '#58A6FF',
      },
      border: {
        default: '#30363D',
        subtle: '#21262D',
        focus: '#388BFF',
        active: '#58A6FF',
      },
      status: {
        success: '#3FB950',
        warning: '#D29922',
        error: '#F85149',
        info: '#58A6FF',
      },
      syntax: {
        comment: '#8B949E',
        string: '#A5D6FF',
        keyword: '#FF7B72',
        function: '#D2A8FF',
        variable: '#FFA657',
        type: '#79C0FF',
        number: '#79C0FF',
        operator: '#FF7B72',
        punctuation: '#E6EDF3',
        class: '#7EE787',
        constant: '#79C0FF',
        decorator: '#D2A8FF',
        property: '#79C0FF',
        parameter: '#FFA657',
        regexp: '#A5D6FF',
        tag: '#7EE787',
        attributeName: '#79C0FF',
        attributeValue: '#A5D6FF',
      },
    },
    fonts: { /* ... */ },
    spacing: { /* ... */ },
  },
  
  'forest-green': {
    metadata: {
      name: 'forest-green',
      version: '4.1.0',
      author: 'MonkeyCode Team',
      displayName: '🌲 森林绿',
      description: '护眼的绿色调,灵感来自自然森林',
      tags: ['dark', 'nature'],
    },
    type: 'dark',
    colors: {
      primary: '#7FD962',
      secondary: '#68BC52',
      accent: '#98E37D',
      background: {
        primary: '#1A2421',
        secondary: '#222E29',
        tertiary: '#2D3D36',
        elevated: '#3A4C43',
      },
      foreground: {
        primary: '#E0EDE6',
        secondary: '#A8C4B5',
        muted: '#6B8A77',
        disabled: '#4A6357',
        link: '#7FD962',
      },
      syntax: {
        comment: '#5C7A6B',
        string: '#B5E8A0',
        keyword: '#F28B82',
        function: '#D4A8FF',
        variable: '#FFCB6B',
        type: '#82DFFF',
        // ... 其他语法颜色
      },
      // ... 其他颜色配置
    },
    fonts: { /* ... */ },
    spacing: { /* ... */ },
  },
  
  'sunset-orange': {
    metadata: {
      name: 'sunset-orange',
      version: '4.1.0',
      author: 'MonkeyCode Team',
      displayName: '🌅 日落橙',
      description: '温暖的橙色系,充满活力与创造力',
      tags: ['dark', 'colorful'],
    },
    type: 'dark',
    colors: {
      primary: '#FF9940',
      secondary: '#E88A35',
      accent: '#FFAD60',
      background: {
        primary: '#1E1814',
        secondary: '#2A211B',
        tertiary: '#382D26',
        elevated: '#483B31',
      },
      foreground: {
        primary: '#F5EDE6',
        secondary: '#C4B5A8',
        muted: '#8A7A6B',
        disabled: '#5E5247',
        link: '#FF9940',
      },
      syntax: {
        comment: '#7A6B5C',
        string: '#FFE0B2',
        keyword: '#FF8A80',
        function: '#EA80FC',
        variable: '#FFD54F',
        type: '#80DEEA',
        // ...
      },
      // ...
    },
    fonts: { /* ... */ },
    spacing: { /* ... */ },
  },
  
  // === 浅色主题 ===
  
  'cloud-white': {
    metadata: {
      name: 'cloud-white',
      version: '4.1.0',
      author: 'MonkeyCode Team',
      displayName: '☁️ 云白',
      description: '清爽明亮的白色主题,适合白天使用',
      tags: ['light', 'minimal'],
    },
    type: 'light',
    colors: {
      primary: '#0969DA',
      secondary: '#0550AE',
      accent: '#218BFF',
      background: {
        primary: '#FFFFFF',
        secondary: '#F6F8FA',
        tertiary: '#EAEEF2',
        elevated: '#FFFFFF',
      },
      foreground: {
        primary: '#1F2328',
        secondary: '#656D76',
        muted: '#8C959F',
        disabled: '#B1BCC8',
        link: '#0969DA',
      },
      syntax: {
        comment: '#6E7781',
        string: '#0A3069',
        keyword: '#CF222E',
        function: '#8250DF',
        variable: '#953800',
        type: '#0550AE',
        // ...
      },
      // ...
    },
    fonts: { /* ... */ },
    spacing: { /* ... */ },
  },
  
  // ... 更多内置主题
};

二、扩展系统架构

2.1 扩展 API 设计

// ===== packages/extensions/src/api/plugin-api.ts =====
/**
 * MonkeyCode 扩展 API
 * 
 * 提供完整的插件开发接口,
 * 让第三方开发者可以扩展 MonkeyCode 的功能
 */

import { EventEmitter } from 'events';
import { z } from 'zod';

// === 扩展元数据 ===
export interface ExtensionManifest {
  // 基本信息
  id: string;                         // 唯一标识: com.example.my-plugin
  name: string;
  version: string;
  description: string;
  
  // 作者信息
  author: {
    name: string;
    email?: string;
    url?: string;
  };
  
  // 兼容性
  engineVersion: string;              // 语义化版本范围: ^4.0.0
  
  // 入口点
  main: string;                       // 入口文件路径
  
  // 权限声明
  permissions: ExtensionPermission[];
  
  // 依赖
  dependencies?: Record<string, string>;
  
  // 激活事件
  activationEvents: ActivationEvent[];
  
  // 贡献点(扩展点注册)
  contributes?: ContributionPoints;
}

// === 权限类型 ===
export enum ExtensionPermission {
  // 文件系统
  READ_FILES = 'fs:read',            // 读取文件
  WRITE_FILES = 'fs:write',          // 写入文件
  
  // 编辑器
  EDIT_DOCUMENT = 'editor:edit',     // 编辑文档
  SHOW_NOTIFICATION = 'ui:notification', // 显示通知
  
  // 网络
  NETWORK_REQUEST = 'network:request', // 网络请求
  
  // AI 功能
  AI_COMPLETION = 'ai:completion',   // AI 补全
  AI_CHAT = 'ai:chat',              // AI 对话
  
  // 配置
  READ_CONFIG = 'config:read',       // 读取配置
  WRITE_CONFIG = 'config:write',     // 写入配置
  
  // Shell
  EXECUTE_SHELL = 'shell:execute',   // 执行 Shell 命令
  
  // 存储
  KEY_VALUE_STORE = 'storage:kvp',   // 键值存储
  SECRET_STORAGE = 'storage:secret', // 安全存储
}

// === 扩展上下文 API ===
export class ExtensionContext extends EventEmitter {
  private _secrets: SecretStorage;
  private _globalState: Memento;
  private _workspaceState: Memento;
  
  /** 扩展绝对路径 */
  readonly extensionPath: string;
  
  /** 扩展存储目录 */
  readonly storagePath: string;
  
  /** 全局状态(跨工作区持久化) */
  get globalState(): Memento {
    return this._globalState;
  }
  
  /** 工作区状态 */
  get workspaceState(): Memento {
    return this._workspaceState;
  }
  
  /** 安全存储(如 API 密钥) */
  get secrets(): SecretStorage {
    return this._secrets;
  }
  
  /**
   * 订阅命令
   */
  registerCommand(
    commandId: string,
    handler: (...args: any[]) => any,
    thisArg?: any,
  ): Disposable {
    return commands.registerCommand(
      `${this.extension.id}.${commandId}`,
      handler,
      thisArg,
    );
  }
  
  /**
   * 注册语言特性
   */
  registerCompletionItemProvider(
    selector: DocumentSelector,
    provider: CompletionItemProvider,
    triggerCharacters?: string[],
  ): Disposable {
    return languages.registerCompletionItemProvider(selector, provider, triggerCharacters);
  }
  
  /**
   * 注册代码操作
   */
  registerCodeActionsProvider(
    selector: DocumentSelector,
    provider: CodeActionsProvider,
    metadata?: CodeActionMetadata,
  ): Disposable {
    return languages.registerCodeActionsProvider(selector, provider, metadata);
  }
  
  /**
   * 注册悬停提示
   */
  registerHoverProvider(
    selector: DocumentSelector,
    provider: HoverProvider,
  ): Disposable {
    return languages.registerHoverProvider(selector, provider);
  }
  
  /**
   * 注册自定义视图
   */
  registerWebviewViewProvider(
    viewId: string,
    provider: WebviewViewProvider,
  ): Disposable {
    return window.registerWebviewViewProvider(viewId, provider);
  }
  
  /**
   * 创建输出通道
   */
  createOutputChannel(name: string): OutputChannel {
    return window.createOutputChannel(`[${this.extension.name}] ${name}`);
  }
  
  /**
   * 显示进度条
   */
  withProgress<R>(
    options: ProgressOptions,
    task: (
      progress: Progress<{ message?: string; increment?: number }>,
      token: CancellationToken,
    ) => Thenable<R>,
  ): Thenable<R> {
    return window.withProgress(options, task);
  }
  
  /**
   * 显示信息消息
   */
  showInformationMessage(
    message: string,
    ...items: string[]
  ): Thenable<string | undefined> {
    return window.showInformationMessage(message, ...items);
  }
  
  /**
   * 访问 AI 引擎
   */
  get ai(): AIExtensionAPI {
    return new AIExtensionAPI(this);
  }
  
  /**
   * 访问主题 API
   */
  get theme(): ThemeExtensionAPI {
    return new ThemeExtensionAPI(this);
  }
}

2.2 扩展贡献点

// ===== packages/extensions/src/api/contribution-points.ts =====
/**
 * 扩展贡献点 (Contribution Points)
 * 
 * 定义扩展可以注入功能的各个位置
 */

export interface ContributionPoints {
  // === 命令 ===
  commands?: Array<{
    command: string;
    title: string;
    category?: string;
    icon?: string;
    keybinding?: string;
    when?: string;                   // 条件表达式
  }>;
  
  // === 菜单 ===
  menus?: Record<string, Array<{
    command: string;
    alt?: string;                     // Alt 点击时执行的命令
    when?: string;
    group?: string;
  }>>;
  
  // === 快捷键 ===
  keybindings?: Array<{
    command: string;
    key: string;
    mac?: string;                    // macOS 专用快捷键
    when?: string;
  }>;
  
  // === 语言支持 ===
  languages?: Array<{
    id: string;
    aliases?: string[];
    extensions: string[];
    configuration?: LanguageConfiguration;
  }>;
  
  // === 语法高亮 ===
  grammars?: Array<{
    language: string;
    scopeName: string;
    path: string;
    injectTo?: string[];
  }>;
  
  // === 代码片段 ===
  snippets?: Array<{
    language: string;
    path: string;
  }>;
  
  // === 主题 ===
  themes?: Array<{
    label: string;
    uiTheme: 'vs-dark' | 'vs-light' | 'hc-black' | 'hc-light';
    path: string;
  }>;
  
  // === 图标主题 ===
  iconThemes?: Array<{
    id: string;
    label: string;
    path: string;
  }>;
  
  // === 自定义视图 ===
  views?: {
    [viewContainerId]: Array<{
      type: string;
      id: string;
      name: string;
      icon?: string;
      when?: string;
    }>;
  };
  
  // === 设置 ===
  configuration?: {
    title: string;
    properties: Record<string, JSONSchema>;
  };
  
  // === AI 增强 ===
  aiEnhancements?: Array<{
    id: string;
    type: 'completion' | 'chat' | 'review' | 'refactor';
    languages?: string[];
    priority: number;
    handler: string;                  // 处理器模块路径
  }>;
  
  // === Webhook ===
  webhooks?: Array<{
    event: string;
    url: string;
    headers?: Record<string, string>;
    secret?: string;
  }>;
  
  // === 外部工具集成 ===
  externalTools?: Array<{
    id: string;
    name: string;
    command: string;
    args?: string[];
    env?: Record<string, string>;
  }>;
}

2.3 扩展开发示例

// ===== example-extension/src/extension.ts =====
/**
 * MonkeyCode 扩展开发示例
 * 
 * 一个完整的扩展实现:
 * 为 SQL 文件添加表结构智能补全
 */

import * as vscode from 'vscode';
import { ExtensionContext, ExtensionManifest } from '@monkeycode/extensions';

const MANIFEST: ExtensionManifest = {
  id: 'com.monkeycode.sql-intellisense',
  name: 'SQL IntelliSense',
  version: '1.0.0',
  description: '为 SQL 文件提供数据库感知的智能补全',
  author: { name: 'MonkeyCode Community' },
  engineVersion: '^4.0.0',
  main: './out/extension.js',
  permissions: [
    ExtensionPermission.READ_CONFIG,
    ExtensionPermission.AI_COMPLETION,
  ],
  activationEvents: [
    'onLanguage:sql',
    'onLanguage:mysql',
    'onLanguage:postgresql',
  ],
  contributes: {
    commands: [
      {
        command: 'sqlIntelliSense.refreshSchema',
        title: '刷新数据库 Schema 缓存',
        category: 'SQL IntelliSense',
      },
      {
        command: 'sqlIntelliSense.showTableInfo',
        title: '显示当前表的详细信息',
        category: 'SQL IntelliSense',
      },
    ],
    configuration: {
      title: 'SQL IntelliSense Settings',
      properties: {
        'sqlIntelliSense.connectionString': {
          type: 'string',
          default: '',
          description: '数据库连接字符串',
          order: 1,
        },
        'sqlIntelliSense.cacheTTL': {
          type: 'number',
          default: 300,
          description: 'Schema 缓存有效期(秒)',
          order: 2,
        },
        'sqlIntelliSense.enableAutoComplete': {
          type: 'boolean',
          default: true,
          description: '启用 SQL 自动补全',
          order: 3,
        },
      },
    },
  },
};

export async function activate(context: ExtensionContext): Promise<void> {
  console.log('🐵 SQL IntelliSense extension activated!');
  
  // 初始化 Schema 缓存管理器
  const schemaCache = new DatabaseSchemaCache(context);
  
  // 注册补全提供者
  const completionProvider = new SQLCompletionProvider(schemaCache);
  
  context.subscriptions.push(
    vscode.languages.registerCompletionItemProvider(
      { scheme: 'file', language: 'sql' },
      completionProvider,
      ' ', '.', '`',  // 触发字符
    ),
    
    vscode.languages.registerCompletionItemProvider(
      { scheme: 'file', language: 'mysql' },
      completionProvider,
      ' ', '.', '`',
    ),
    
    vscode.languages.registerHoverProvider(
      { scheme: 'file', language: ['sql', 'mysql', 'postgresql'] },
      new SQLHoverProvider(schemaCache),
    ),
    
    // 注册命令
    vscode.commands.registerCommand(
      'sqlIntelliSense.refreshSchema',
      () => schemaCache.refresh(),
    ),
    
    vscode.commands.registerCommand(
      'sqlIntelliSense.showTableInfo',
      () => showTableInfo(schemaCache),
    ),
  );
  
  // 定期刷新缓存
  const refreshInterval = setInterval(() => {
    schemaCache.refreshIfNeeded();
  }, 5 * 60 * 1000); // 每5分钟检查一次
  
  context.subscriptions.push(new Disposable(() => clearInterval(refreshInterval)));
}

class SQLCompletionProvider implements vscode.CompletionItemProvider {
  private cache: DatabaseSchemaCache;
  
  constructor(cache: DatabaseSchemaCache) {
    this.cache = cache;
  }
  
  async provideCompletionItems(
    document: vscode.TextDocument,
    position: vscode.Position,
    token: vscode.CancellationToken,
    context: vscode.CompletionContext,
  ): Promise<vscode.CompletionItem[]> {
    const textBeforeCursor = document.getText(
      new vscode.Range(new vscode.Position(position.line, 0), position)
    );
    
    const items: vscode.CompletionItem[] = [];
    
    // 表名补全
    if (/FROM\s+$/i.test(textBeforeCursor) || /JOIN\s+$/i.test(textBeforeCursor)) {
      const tables = await this.cache.getTables();
      for (const table of tables) {
        items.push(new vscode.CompletionItem(table.name, vscode.CompletionItemKind.Class));
        items[items.length - 1].documentation = table.comment || `Table: ${table.name}`;
        items[items.length - 1].detail = `${table.columns.length} columns`;
      }
    }
    
    // 列名补全
    if (/SELECT\s+\w*$/i.test(textBeforeCursor) || /WHERE\s+\w*$/i.test(textBeforeCursor)) {
      const tableName = this.extractTableName(textBeforeCursor);
      if (tableName) {
        const columns = await this.cache.getTableColumns(tableName);
        for (const col of columns) {
          const item = new vscode.CompletionItem(col.name, vscode.CompletionItemKind.Field);
          item.detail = col.type;
          item.documentation = col.comment || `Column: ${col.name} (${col.type})`;
          
          // 根据 SQL 关键字插入不同格式
          if (/SELECT/i.test(textBeforeCursor)) {
            item.insertText = col.name;
          } else {
            item.insertText = `${col.name} = $0`;
          }
          
          items.push(item);
        }
      }
    }
    
    return items;
  }
  
  private extractTableName(sql: string): string | null {
    // 从 SQL 中提取最近的表名
    const fromMatch = sql.match(/(?:FROM|JOIN)\s+(\w+)/i);
    return fromMatch ? fromMatch[1] : null;
  }
}

三、主题编辑器

3.1 可视化主题编辑器

// ===== packages/theme-editor/src/editor.ts =====
/**
 * MonkeyCode 可视化主题编辑器
 * 
 * 提供所见即所得的主题定制体验
 */

import { Theme, ThemeSchema } from '@monkeycode/theme';

export class ThemeEditor {
  private currentTheme: Theme;
  private previewFrame: HTMLIFrameElement;
  private colorPickers: Map<string, ColorPicker> = new Map();
  
  constructor(container: HTMLElement, initialTheme: Theme) {
    this.currentTheme = initialTheme;
    this.render(container);
  }
  
  private render(container: HTMLElement): void {
    container.innerHTML = `
      <div class="theme-editor">
        <div class="editor-layout">
          <!-- 左侧:属性面板 -->
          <div class="properties-panel">
            ${this.renderColorSection()}
            ${this.renderFontSection()}
            ${this.renderSpacingSection()}
            ${this.renderCSSSection()}
          </div>
          
          <!-- 中间:预览区域 -->
          <div class="preview-panel">
            <div class="preview-toolbar">
              <select id="preview-mode">
                <option value="editor">代码编辑器</option>
                <option value="chat">对话面板</option>
                <option value="review">审查报告</option>
                <option value="settings">设置页面</option>
              </select>
              <button id="toggle-dark-light">切换明暗</button>
              <button id="export-theme">导出主题</button>
              <button id="share-theme">分享主题</button>
            </div>
            <iframe class="preview-frame" id="preview"></iframe>
          </div>
          
          <!-- 右侧:代码视图 -->
          <div class="code-panel">
            <div class="code-tabs">
              <button class="tab active" data-tab="json">JSON</button>
              <button class="tab" data-tab="css">CSS Variables</button>
              <button class="tab" data-tab="typescript">TypeScript</button>
            </div>
            <pre><code id="theme-code"></code></pre>
          </div>
        </div>
      </div>
    `;
    
    this.previewFrame = container.querySelector('#preview') as HTMLIFrameElement;
    this.bindEvents();
    this.updatePreview();
    this.updateCodeView();
  }
  
  private renderColorSection(): string {
    const { colors } = this.currentTheme;
    
    return `
      <section class="section">
        <h3>🎨 颜色</h3>
        
        <div class="color-group">
          <h4>品牌色</h4>
          <div class="color-row">
            ${this.colorInput('primary', colors.primary, '主色调')}
            ${this.colorInput('secondary', colors.secondary, '辅助色')}
            ${this.colorInput('accent', colors.accent, '强调色')}
          </div>
        </div>
        
        <div class="color-group">
          <h4>背景色</h4>
          <div class="color-row">
            ${this.colorInput('background.primary', colors.background.primary, '主背景')}
            ${this.colorInput('background.secondary', colors.background.secondary, '次级背景')}
            ${this.colorInput('background.tertiary', colors.background.tertiary, '三级背景')}
            ${this.colorInput('background.elevated', colors.background.elevated, '浮起元素')}
          </div>
        </div>
        
        <div class="color-group">
          <h4>前景色(文字)</h4>
          <div class="color-row">
            ${this.colorInput('foreground.primary', colors.foreground.primary, '主文字')}
            ${this.colorInput('foreground.secondary', colors.foreground.secondary, '次要文字')}
            ${this.colorInput('foreground.muted', colors.foreground.muted, '弱化文字')}
            ${this.colorInput('foreground.link', colors.foreground.link, '链接')}
          </div>
        </div>
        
        <div class="color-group">
          <h4>语法高亮</h4>
          <div class="color-grid">
            ${Object.entries(colors.syntax).map(([key, value]) => 
              this.colorInput(`syntax.${key}`, value, key)
            ).join('')}
          </div>
        </div>
        
        <div class="color-group">
          <h4>状态色</h4>
          <div class="color-row">
            ${this.colorInput('status.success', colors.status.success, '成功 ✅')}
            ${this.colorInput('status.warning', colors.status.warning, '警告 ⚠️')}
            ${this.colorInput('status.error', colors.status.error, '错误 ❌')}
            ${this.colorInput('status.info', colors.status.info, '信息 ℹ️')}
          </div>
        </div>
      </section>
    `;
  }
  
  private colorInput(path: string, value: string, label: string): string {
    return `
      <div class="color-input-wrapper">
        <label>${label}</label>
        <div class="color-input">
          <input type="color" value="${value}" data-path="${path}" />
          <input type="text" value="${value}" data-path="${path}" class="hex-input" />
        </div>
      </div>
    `;
  }
  
  /**
   * 更新实时预览
   */
  private updatePreview(): void {
    const cssVariables = this.generateCSSVariables();
    const previewHTML = this.generatePreviewHTML(cssVariables);
    
    this.previewFrame.srcdoc = previewHTML;
  }
  
  /**
   * 从主题生成 CSS 变量
   */
  private generateCSSVariables(): string {
    const { colors, fonts, spacing } = this.currentTheme;
    
    let vars = ':root {\n';
    
    // 颜色变量
    const flattenColors = (obj: any, prefix: string = '') => {
      for (const [key, value] of Object.entries(obj)) {
        if (typeof value === 'object' && value !== null) {
          flattenColors(value, `${prefix}-${key}`);
        } else {
          vars += `  --mc${prefix}-${key}: ${value};\n`;
        }
      }
    };
    flattenColors(colors, '--color');
    
    // 字体变量
    vars += `\n  --mc-font-mono: ${fonts.fontFamily.mono};\n`;
    vars += `  --mc-font-sans: ${fonts.fontFamily.sans};\n`;
    vars += `  --mc-font-size-base: ${fonts.fontSize.base}px;\n`;
    
    // 间距变量
    vars += `  --mc-spacing-unit: ${spacing.unit}px;\n`;
    
    vars += '}';
    
    return vars;
  }
  
  /**
   * 导出主题为可分发格式
   */
  export(format: 'json' | 'css' | 'package'): string {
    switch (format) {
      case 'json':
        return JSON.stringify(this.currentTheme, null, 2);
      
      case 'css':
        return this.generateFullCSS();
      
      case 'package':
        return this.generatePackageJSON();
    }
  }
}

四、社区主题市场

4.1 主题市场架构

// ===== packages/marketplace/src/theme-market.ts =====
/**
 * MonkeyCode 主题市场
 * 
 * 发现、安装、管理和分享社区主题
 */

export interface MarketTheme {
  id: string;
  name: string;
  author: AuthorInfo;
  version: string;
  description: string;
  
  // 元数据
  tags: string[];
  downloads: number;
  rating: number;
  ratingCount: number;
  
  // 预览
  screenshots: string[];
  previewGif?: string;
  
  // 安装信息
  themePath: string;
  manifest: Theme;
  
  // 来源
  source: 'official' | 'community' | 'verified';
  
  // 兼容性
  minEngineVersion: string;
  lastUpdated: Date;
}

class ThemeMarketplace {
  private registryUrl = 'https://market.monkeycode.ai';
  private installedThemes = new Map<string, InstalledTheme>();
  
  /**
   * 搜索主题
   */
  async searchThemes(query: SearchQuery): Promise<MarketTheme[]> {
    const params = new URLSearchParams({
      q: query.query || '',
      sort: query.sort || 'popularity',
      order: query.order || 'desc',
      limit: String(query.limit || 20),
      offset: String(query.offset || 0),
      ...(query.tags ? { tags: query.tags.join(',') } : {}),
      ...(query.darkOnly ? { dark: 'true' } : {}),
      ...(query.lightOnly ? { light: 'true' } : {}),
    });
    
    const response = await fetch(`${this.registryUrl}/api/themes?${params}`);
    const data = await response.json();
    
    return data.themes.map((t: any) => this.normalizeMarketTheme(t));
  }
  
  /**
   * 安装主题
   */
  async installTheme(themeId: string): Promise<InstalledTheme> {
    // 1. 获取主题元数据
    const meta = await fetch(`${this.registryUrl}/api/themes/${themeId}`);
    const themeData = await meta.json();
    
    // 2. 下载主题包
    const downloadUrl = themeData.downloadUrl;
    const response = await fetch(downloadUrl);
    const buffer = await response.arrayBuffer();
    
    // 3. 解压并验证
    const themeDir = path.join(this.themesDir, themeId);
    await fs.mkdir(themeDir, { recursive: true });
    await extractZip(buffer, themeDir);
    
    // 4. 验证 Schema
    const manifestPath = path.join(themeDir, 'theme.json');
    const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
    const validated = ThemeSchema.parse(manifest);
    
    // 5. 注册到本地
    const installed: InstalledTheme = {
      id: themeId,
      manifest: validated,
      installPath: themeDir,
      installedAt: new Date(),
      enabled: true,
    };
    
    this.installedThemes.set(themeId, installed);
    await this.saveInstalledList();
    
    // 6. 触发主题变更事件
    this.emit('installed', installed);
    
    return installed;
  }
  
  /**
   * 发布主题到市场
   */
  async publishTheme(themePath: string, options: PublishOptions): Promise<PublishResult> {
    // 1. 验证主题
    const manifestPath = path.join(themePath, 'theme.json');
    const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
    
    // Schema 验证
    const result = ThemeSchema.safeParse(manifest);
    if (!result.success) {
      throw new Error(`主题验证失败: ${result.error.message}`);
    }
    
    // 2. 打包
    const packageBuffer = await createZip(themePath, {
      ignore: ['.git', 'node_modules', '.DS_Store', '*.ts'],
    });
    
    // 3. 上传
    const formData = new FormData();
    formData.append('package', new Blob([packageBuffer]), 'theme.zip');
    formData.append('manifest', JSON.stringify(manifest));
    formData.append('token', options.token);
    
    const response = await fetch(`${this.registryUrl}/api/themes/publish`, {
      method: 'POST',
      body: formData,
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || '发布失败');
    }
    
    const result = await response.json();
    
    return {
      success: true,
      themeId: result.themeId,
      publishedUrl: result.url,
      version: manifest.metadata.version,
    };
  }
}

4.2 热门社区主题

主题名 作者 下载量 ⭐ 评分 类型 特色
Dracula Pro @zenorocha 128K 4.9 Dark 经典 Dracula 配色
Nord @arcticicestudio 95K 4.9 Dark 北极光灵感的冷色调
Tokyo Night @enkia 87K 4.8 Dark 夜晚东京霓虹风格
Catppuccin @catppuccin 76K 4.9 Dark/Light 温暖的 SOULful 配色
One Dark Pro @binaryify 72K 4.7 Dark Atom One Dark 移植
Solarized Light @ethanschoonover 64K 4.8 Light 护眼低对比度浅色
Gruvbox @morhetz 58K 4.8 Dark 复古终端配色
Rose Pine @rose-pine 45K 4.9 Dark/Light 优雅的玫瑰松色系
Material @equinusocio 42K 4.7 Dark/Light Material Design 配色
GitHub Sharp @princiwoj 38K 4.6 Light GitHub 浅色增强版

五、高级定制技巧

5.1 创建完整自定义主题

# ===== 快速创建主题模板 =====
$ monkeycode theme init my-awesome-theme

✅ Created theme directory: ./my-awesome-theme/
📄 Created theme.json (manifest)
🎨 Created colors.json (color tokens)
🖋️  Created fonts.json (font config)
📐 Created spacing.json (spacing system)
🖼️  Created screenshot.png placeholder
📝 Created README.md template
📦 Created package.json for publishing

# 编辑主题文件后,预览效果
$ monkeycode theme preview ./my-awesome-theme/

# 验证并打包
$ monkeycode theme validate ./my-awesome-theme/
$ monkeycode theme build ./my-awesome-theme/

# 发布到市场
$ monkeycode theme publish ./my-awesome-theme/ \
  --token YOUR_API_TOKEN \
  --category dark \
  --tags "minimal professional blue"

5.2 高级 CSS 覆盖技巧

/* ===== 自定义主题的高级 CSS 覆盖 ===== */

/* 1. 自定义滚动条样式 */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: var(--mc-color-background-secondary);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb {
  background: var(--mc-color-border-default);
  border-radius: 4px;
  
  &:hover {
    background: var(--mc-color-foreground-muted);
  }
}

/* 2. AI 补全结果的自定义动画 */
@keyframes completion-fade-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.mc-completion-item {
  animation: completion-fade-in 0.15s ease-out;
  
  &:hover {
    background: var(--mc-color-background-elevated);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  }
}

/* 3. 代码审查标记的特殊样式 */
.mc-review-error {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--mc-color-status-error) 0%,
    var(--mc-color-status-error) 3%,
    transparent 3%
  );
  background-size: 200% 100%;
  animation: review-highlight 2s ease infinite;
}

@keyframes review-highlight {
  0%, 100% { background-position: 100% 0; }
  50% { background-position: -100% 0; }
}

/* 4. 对话气泡渐变背景 */
.mc-chat-bubble-assistant {
  background: linear-gradient(
    135deg,
    var(--mc-color-background-tertiary) 0%,
    var(--mc-color-background-secondary) 100%
  );
  border-left: 3px solid var(--mc-color-primary);
}

/* 5. 状态栏毛玻璃效果 */
.mc-status-bar {
  backdrop-filter: blur(12px);
  background: rgba(
    var(--mc-color-background-primary-rgb),
    0.85
  );
  border-top: 1px solid var(--mc-color-border-subtle);
}

六、性能优化

6.1 主题加载性能

优化策略 效果 实现方式
CSS 变量动态切换 主题切换 < 16ms 仅修改 CSS 变量值,无需重载
按需加载组件主题 初始加载减少 40% 懒加载非活跃面板的主题资源
主题缓存 重复打开零延迟 IndexedDB 缓存已解析的主题对象
增量更新 修改即时生效 只更新变化的 CSS 变量
GPU 加速动画 动画不掉帧 transform/opacity 触发 GPU 合成

6.2 扩展性能保障

// ===== packages/extensions/src/sandbox.ts =====
/**
 * 扩展沙箱 — 性能和安全保障
 */

export class ExtensionSandbox {
  /**
   * 资源限制
   */
  private limits = {
    maxMemoryMB: 256,                // 最大内存
    maxCPUUsage: 30,                 // 最大 CPU 占用百分比
    maxNetworkRequestsPerMin: 60,    // 每分钟最大网络请求数
    maxFileOperationsPerSec: 10,     // 每秒最大文件操作数
    startupTimeoutMs: 5000,          // 启动超时
    activationTimeoutMs: 3000,       // 激活超时
  };
  
  /**
   * 监控扩展性能
   */
  monitor(extensionId: string): PerformanceMonitor {
    return {
      onMemoryWarning: (usage) => {
        console.warn(`⚠️ Extension ${extensionId} memory usage high: ${usage}MB`);
        if (usage > this.limits.maxMemoryMB * 0.9) {
          this.throttle(extensionId, 'memory');
        }
      },
      onCPUSpike: (usage) => {
        if (usage > this.limits.maxCPUUsage) {
          console.warn(`⚠️ Extension ${extensionId} CPU spike: ${usage}%`);
        }
      },
      onSlowOperation: (operation, duration) => {
        if (duration > 1000) {
          console.warn(`⏱️ Slow operation in ${extensionId}: ${operation} took ${duration}ms`);
        }
      },
    };
  }
  
  /**
   * 对超限扩展进行节流
   */
  private throttle(extensionId: string, reason: string): void {
    // 降低该扩展的事件处理优先级
    // 限制其 API 调用频率
    // 必要时暂停扩展
  }
}

结语

"最好的工具是那些能被你塑造的工具——而不是强迫你适应它的工具。"

MonkeyCode 的主题与扩展系统体现了我们对开发者个性化体验的深度尊重:

  • 🎨 8 款精心设计的内置主题 — 开箱即用的视觉享受
  • 🔧 完整的主题编辑器 — 所见即所得的定制体验
  • 🛒 繁荣的主题市场 — 50+ 社区主题一键安装
  • 🔌 强大的扩展 API — 无限的功能扩展可能
  • 性能优化的沙箱机制 — 扩展运行安全高效

无论你是追求极简美学的艺术家型开发者,还是注重效率的工具控,或是需要统一团队规范的团队管理者,MonkeykeyCode 的主题与扩展系统都能满足你的需求。

💬 参与方式

MonkeyCode — 你的工具,你的风格,你的规则。 🐵🎨✨

posted on 2026-06-30 13:38  MonkeyCode  阅读(19)  评论(0)    收藏  举报