Compare commits

...

26 Commits

Author SHA1 Message Date
ydy0615 05b1cbf80d chore: 更新项目配置和前后端代码优化 2026-04-04 20:10:24 +08:00
ydy0615 ef162de168 modified: backend/requirements.txt 2026-03-14 20:48:44 +08:00
ydy0615 1155de4867 feat(MilkdownEditor): add file upload support for documents and text files
Added new upload functionality to the editor supporting doc/docx/ppt/pptx/pdf/zip/txt/json files. Includes:
- New upload button with file input
- File type detection utilities (isTextFile, isConvertibleFile)
- Initial markdown sync with trailing whitespace normalization
- Warning messages for unsupported file types
2026-03-14 19:24:15 +08:00
ydy0615 d452d1747e feat: add language synonym mapping and canonicalization
Add LANGUAGE_SYNONYMS dictionary to map language aliases to canonical IDs,
_canonical_language_id() to normalize language identifiers, and
_language_guidance() to provide language-specific instructions for LLM
code generation. This improves language detection and ensures consistent
prompt context across different language format variations.
2026-03-14 18:20:39 +08:00
ydy0615 c0d4bf8b2b chore: remove unnecessary comments and suppress error logging 2026-03-10 23:26:18 +08:00
ydy0615 8d89c2a0f6 Merge remote changes with local modifications
- Add docx and html2pdf.js export functionality (from remote)
- Update backend with new API endpoints
- Sync local configuration changes
2026-03-10 23:10:11 +08:00
ydy0615 2ad57887cd feat: add docx and html2pdf.js for document export functionality 2026-03-10 22:21:11 +08:00
ydy0615 637456ee34 feat(api): add completion request cancellation and mermaid rendering
Add support for cancelling in-progress LLM completion requests via new /v1/completions/cancel endpoint with task tracking. Implement mermaid diagram rendering in the Milkdown editor with a new mermaidPlugin. Update copilotPlugin to properly abort requests with descriptive reasons. Refactor settings panel to handle system theme changes reactively. Add camera capture support for image uploads.
2026-02-25 19:00:17 +08:00
ydy0615 e28125079c feat(api): add system prompt support for LLM completion
Separate prompt generation into system and user prompts for better LLM instruction following. Backend now builds a detailed system prompt with constraints for math formatting, code block handling, boundary newlines, and OCR safety, while user prompt contains context and completion state flags. Added corresponding tests for both modules.
2026-02-23 15:17:36 +08:00
ydy0615 ce0731c2f2 feat(api): allow X-API-Key and X-Client-IP headers in CORS configuration 2026-02-19 20:26:56 +08:00
ydy0615 e77f69c5c4 chore(config): update default API base URL to production endpoint 2026-02-19 20:15:28 +08:00
ydy0615 5434f3eb47 fix(backend): rename thinking parameter to think for Ollama API compatibility
The Ollama API expects "think" parameter instead of "thinking". Also updates the API base URL in the frontend configuration to point to the correct endpoint.
2026-02-19 18:55:49 +08:00
ydy0615 4a979ba7c3 feat(api): add API key authentication to backend endpoints
Add API key security using fastapi.security.APIKeyHeader to protect /v1/completions and /v1/ocr endpoints. Updated frontend to include X-API-Key header in API requests. Also changed default API base URL from http://149.104.29.239:8001 to https://api.learnteach.tech:8002.
2026-02-19 18:18:47 +08:00
ydy0615 4fe4becdd5 fix(backend): filter out <br> tags from prompt context
Filter out potential web-scraping or legacy artifacts like <br>, <br/>, <br\> from prefix/suffix before using in model completion context
2026-02-19 11:14:43 +08:00
ydy0615 065b4ac319 feat(llm): add thinking parameter support for Ollama API calls
Add optional thinking parameter to the call_ollama function and pass it from the request. Also enhance timezone handling in prompt generation to support configurable timezone preferences.
2026-02-19 10:34:31 +08:00
ydy0615 aa6133e3ed feat: add privacy mode, thinking levels, PWA support, and i18n
- Add privacy mode to hide IP and user preferences from AI requests
- Add model thinking levels (low/medium/high) for context analysis depth
- Add PWA support with service worker, manifest, and app icons
- Add SettingsPanel for user preferences (theme, background, language)
- Add i18n translations for en/zh/ja/ko/de/fr
- Add Pinia store for centralized settings management
- Update backend to support user preferences and thinking levels
- Update config to use absolute API URLs
2026-02-19 10:22:27 +08:00
ydy0615 d2b64ad5d6 feat: add IP geolocation tracking and include location in prompts
- Add GeoLite2-City.mmdb database for IP lookup
- Create geoip.py module for IP location services
- Extract client IP from requests and log location info
- Pass location context to LLM prompts for enhanced responses
2026-02-18 08:59:28 +08:00
ydy0615 2b79f20e19 chore(config): update default Ollama model to gpt-oss:120b
Remove obsolete planning documents that are no longer needed:
- copilot-prompt-system-analysis.md
- ghost-text-markdown-rendering.md
- image-button-plan.md
- image-processing-plan.md
- refactor-backend.md
2026-02-17 09:27:21 +08:00
ydy0615 d9418fac98 refactor(copilot): simplify ghost mark addition by removing conditional check
Remove the mark type allowance validation and apply ghost marks directly
to text nodes within the specified range.
2026-02-16 22:58:11 +08:00
ydy0615 0d25f4d1ef Remove unused test files and tokenization modules
- Deleted `windowDelineation.test.ts` as it is no longer needed.
- Removed `index.ts` and `tokenizer.ts` from the tokenization module due to refactoring.
- Eliminated unused types related to authentication, code citation, context provider API, and core functionalities.
- Cleaned up the `status.ts` file by removing the `StatusKind` type definition.
2026-02-16 22:49:32 +08:00
“ydy0615” 71a71530a3 feat(ui): update editor welcome message to Chinese 2026-02-16 09:05:43 +08:00
“ydy0615” 9b37ca42d6 chore: remove env files and update gitignore for python
Remove sensitive environment files from repository tracking and add comprehensive Python ignore patterns including virtual environments, cache files, and environment variables. Also clean up staged __pycache__ binary files.
2026-02-16 09:01:08 +08:00
ydy0615 075eded2ba modified: .env
modified:   .env.example
2026-02-15 23:32:04 +08:00
ydy0615 eb6e8bbfff refactor(config): use Vite proxy for API requests and change backend port
- Configure Vite dev server to proxy /v1 requests to backend
- Change backend port from 8000 to 8001
- Simplify API URLs to relative paths instead of absolute localhost URLs
- Increase debounce delay from 500ms to 1000ms for better stability
- Update README documentation to reflect all changes
2026-02-15 23:23:00 +08:00
ydy0615 1e58c18bbc feat: add image hash-based OCR caching with 100MB size limit
- Implement SHA-256 image hashing to cache OCR results and avoid re-processing identical images
- Add 100MB file size limit for image uploads with user-friendly error messages
- Clear ghost suggestions when uploading new images to prevent interference
- Optimize size limit calculation in copilot plugin to include OCR context
- Remove debug logging from production code
- Add image processing optimization plan document

BREAKING CHANGE: Image upload size limit is now enforced at 100MB (previously unlimited)
2026-02-15 22:17:37 +08:00
ydy0615 190bb2b756 feat(ui): enhance theme toggle styling and add Chinese localization
- Redesign theme toggle with improved visual effects including gradient overlays, enhanced shadows, and smoother cubic-bezier transitions
- Update toggle dimensions and icon positioning for better visual balance
- Add SVG filter effects for sun/moon icons in dark mode
- Replace English UI text with Chinese localization in MilkdownEditor
- Refactor copilotPlugin by removing unused decoration functions and improving ghost mark text node handling
2026-02-15 18:24:34 +08:00
402 changed files with 8699 additions and 122416 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"shortcuts": [
{
"label": "Run",
"command": "npm run dev",
"icon": "play"
}
]
}
-1
View File
@@ -1 +0,0 @@
VITE_API_URL=http://localhost:8000/v1/completions
+4 -11
View File
@@ -1,11 +1,4 @@
VITE_API_URL=http://localhost:8000/v1/completions VITE_API_BASE_URL=
VITE_OCR_URL=http://localhost:8000/v1/ocr VITE_API_URL=
VITE_OCR_URL=
# Ollama 配置 VITE_CONVERT_URL=
OLLAMA_HOST=http://192.168.0.120:11434
OLLAMA_MODEL=gpt-oss:120b
# 可选:其他配置
# 如果ollama需要认证,可以使用以下变量
# OLLAMA_USERNAME=your_username
# OLLAMA_PASSWORD=your_password
+17
View File
@@ -12,6 +12,23 @@ dist
dist-ssr dist-ssr
*.local *.local
# Python
__pycache__/
*.py[cod]
*.pyc.*
.python-version
.venv/
venv/
env/
.pytest_cache/
.mypy_cache/
.ruff_cache/
# Env files
.env
.env.*
!.env.example
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
!.vscode/extensions.json !.vscode/extensions.json
+6 -4
View File
@@ -1,9 +1,11 @@
# rules.md # rules.md
在构建这个LLM应用网页时,你需要基于VUE3开发。我需要前端只运行渲染和数据回传,后端负责llm api调用,inline suggustions实现和数据解析。 在构建这个LLM应用网页时,你需要基于VUE3开发。我需要前端只运行渲染和数据回传,后端负责llm api调用,类似copilet的auto inline suggustions实现和数据解析。
## 指导原则 ## 指导原则
- 不要擅自用npm或者yarn运行网页,你既看不到网页的内容,也无法阻止命令暂停 - 不要擅自用npm或者yarn运行网页,你既看不到网页的内容,也无法阻止命令暂停。但是,你可以用npm run build检查代码。
- 应该保证代码效率,不多定义变量,不写冗余注释,把降低延迟放在第一位 - 应该保证代码效率,不多定义变量,不写冗余注释,把降低延迟放在第一位
- 每次完成任务前都要反复检查代码,确保代码准确无误 - 每次完成任务前都要反复阅读检查代码,确保代码准确无误
- 尽量不要搜索关键字,而是了解代码结构后查询整个问题代码明确问题所在。
- @/milkdown-docs/ 代表milkdown的最新官方文档,不要修改,涉及到前端编辑器的指令时要核对官方文档。
+52
View File
@@ -0,0 +1,52 @@
# 仓库指南
## 语言约定
项目文档、日志、错误提示以及对外返回的文字信息统一使用 **中文**。前端 UI 默认展示中文,若需多语言支持请在相应模块实现。
## 项目结构 \& 模块组织
```
backend/ # FastAPI 后端(Python
├─ main.py # API 入口
├─ llm.py # LLM 包装工具
├─ prompt.py # Prompt 构建辅助
└─ tests/ # pytest 测试套件
public/ # 前端静态资源
src/ # 前端源码(Vite + React
dist/ # 构建产出(生成文件)
```
生产代码主要位于 `backend/`Python)和 `src/`(JS/TS)。测试文件与被测模块并置。
## 构建、测试、开发命令
| 命令 | 说明 |
|----------------------------------------------|--------------------------------------------------|
| `npm install` | 安装前端依赖 |
| `npm run dev` | 启动 Vite 开发服务器 |
| `uvicorn backend.main:app --reload` | 本地运行 FastAPI 服务 |
| `pytest` | 运行 Python 测试套件 |
| `npm run build` | 生成生产环境构建产物至 `dist/` |
## 编码风格 \& 命名约定
- **Python**:使用 4 空格缩进,`snake_case` 命名函数/变量,`PascalCase` 命名类。提交前请使用 `ruff`/`black` 格式化。
- **JavaScript/TypeScript**:使用 2 空格缩进,`camelCase` 命名变量/函数,`PascalCase` 命名 React 组件。使用 `eslint``prettier` 检查。
- 文件名采用全小写加短横线,例如 `my-module.py``my-component.tsx`
## 测试指南
- 后端使用 **pytest**,测试文件放在对应模块目录下,命名为 `test_<module>.py`
- 目标覆盖率 ≥ 80%`pytest --cov=backend`)。
- 在虚拟环境中运行:`pip install -r backend/requirements.txt && pytest`
## 提交 \& Pull Request 规范
- 提交信息遵循 **Conventional Commits**`feat:` 新功能、`fix:` 修复、`docs:` 文档、`refactor:` 重构等。
- PR 必须包含:
- 与提交信息匹配的标题。
- 关联的 Issue(如 `Fixes #123`)。
- UI 变更或 API 示例的截图/示例。
- 所有 CI 检查(代码检查、测试、类型检查)均通过。
## 安全 \& 配置建议
- 敏感信息请放入 `.env` 并确保已在 `.gitignore` 中。
- 按照 `backend/main.py` 中的实现,对上传文件的大小和类型进行校验,防止滥用。
- 定期审计依赖安全(`npm audit``pip-audit`)。
---
以上指南旨在保持贡献一致性并维护代码库健康,欢迎通过 Pull Request 提出改进。
+814
View File
@@ -0,0 +1,814 @@
# llm-in-text 修复清单(匿名可用版)
## 说明
这不是审计报告。
这份文档只回答三件事:
1. 现在具体哪里有问题
2. 问题为什么会发生
3. 应该怎么改
前提按你的要求处理:
- 网站是匿名可用的
- 不做用户登录
- 不做用户身份体系
- 但仍然要防止接口被滥用、站点被刷爆、服务被恶意调用
匿名可用不等于完全不做保护。
对于这种网站,正确做法通常是:
- 不做用户登录
- 不在前端放任何真正的服务端秘密
- 用服务端限流、来源限制、请求大小限制、网关策略保护接口
- 必要时用站点级防刷手段,而不是用户级登录
---
## 1. 前端硬编码了服务端 API Key
### 具体问题
- [src/utils/api.js:4](/C:/Users/ydy/Desktop/llm-in-text/src/utils/api.js#L4)
- [src/utils/convert.js:3](/C:/Users/ydy/Desktop/llm-in-text/src/utils/convert.js#L3)
代码里把:
```js
const API_KEY = 'your-secret-key-here'
```
直接写进了前端源码。
### 错误原因
前端代码最终会发到浏览器里。
只要用户能打开网站,就一定能在浏览器开发者工具、打包产物、网络请求里看到这个 key。
所以前端里的“密钥”根本不是密钥,只是公开字符串。
### 会导致什么
- 任何人都可以绕过你的网站,直接写脚本刷你的后端
- 这个 key 一旦被复制,就等于后端公开可调用
### 整改方式
你的场景不做登录,所以最简单、正确的做法是:
1. 删除前端里的 `API_KEY`
2. 后端不要再要求前端传固定共享 key
3. 改成下面这套匿名保护方案:
- 只允许来自你站点域名的浏览器请求
- 网关层限流
- 接口级限流
- 请求体大小限制
- 必要时加站点级验证码或 challenge,而不是登录
### 你应该改成什么
- `src/utils/api.js` 不再发 `X-API-Key`
- `src/utils/convert.js` 不再发 `X-API-Key`
- `backend/main.py` 删除固定 `API_KEY` 和对应校验逻辑
### 验收标准
- 全仓库搜不到 `your-secret-key-here`
- 前端请求头中不再包含固定共享 key
---
## 2. 后端 CORS 过宽
### 具体问题
- [backend/main.py:34](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L34)
- [backend/main.py:35](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L35)
- [backend/main.py:36](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L36)
- [backend/main.py:37](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L37)
现在配置是:
- `allow_origins=["*"]`
- `allow_credentials=True`
- `allow_methods=["*"]`
- `allow_headers=["*"...]`
### 错误原因
这是开发期常见的“先全开让它跑起来”的写法。
但生产里这样做会让任何站点都能更容易发起跨域调用。
### 会导致什么
- 其他网站更容易借你的浏览器接口能力
- 以后一旦加 cookie、session、任何凭据,会立刻放大风险
### 整改方式
既然你是匿名站点,不做登录,那就更应该把跨域收紧:
1. 只允许你的正式域名和本地开发域名
2. 不要开 `allow_credentials=True`,匿名站一般不需要
3. 只开放需要的方法和头
### 建议改法
把:
```python
allow_origins=["*"]
allow_credentials=True
allow_methods=["*"]
allow_headers=["*", "X-API-Key", "X-Client-IP", "X-Request-Id"]
```
改成类似:
```python
allow_origins=[
"https://your-domain.com",
"https://www.your-domain.com",
"http://localhost:5173",
]
allow_credentials=False
allow_methods=["POST", "OPTIONS"]
allow_headers=["Content-Type", "X-Request-Id"]
```
### 验收标准
- 非你自己域名的网页无法直接跨域调用你的接口
- 不再开放无用头和无用方法
---
## 3. 默认会去拿用户公网 IP,并发送给后端
### 具体问题
- [src/stores/settings.js:16](/C:/Users/ydy/Desktop/llm-in-text/src/stores/settings.js#L16)
- [src/utils/api.js:54](/C:/Users/ydy/Desktop/llm-in-text/src/utils/api.js#L54)
- [src/utils/api.js:100](/C:/Users/ydy/Desktop/llm-in-text/src/utils/api.js#L100)
- [backend/main.py:110](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L110)
流程是:
1. 前端默认 `privacyMode = false`
2. 前端请求 `https://api.ipify.org?format=json`
3. 获取公网 IP
4. 放进 `X-Client-IP`
5. 后端再做地理位置推断
### 错误原因
这是把“个性化上下文”做成了默认行为。
但对匿名站点来说,这不是必要信息。
### 会导致什么
- 页面会额外访问第三方服务
- 用户 IP 会进入你的请求链路
- 模型上下文中会混入地理位置信息
### 整改方式
如果你不需要真正的按地理位置个性化,就最简单:
1. 删除 `getClientIP()`
2. 删除调用 `api.ipify.org`
3. 删除 `X-Client-IP`
4. 后端删除 GeoIP 逻辑
5. `privacyMode` 可以保留,但默认应是更安全的行为
### 你应该删什么
- `src/utils/api.js` 中的 `getClientIP`
- `headers['X-Client-IP'] = clientIP`
- `backend/main.py``get_client_ip`
- `location = get_ip_location_text(client_ip)`
- `geoip.py` 如果以后不用可以移除
### 验收标准
- 前端网络面板中不再出现 `api.ipify.org`
- 后端不再接收 `X-Client-IP`
- prompt 不再包含用户位置
---
## 4. 后端把内部异常原样返回给前端
### 具体问题
- [backend/main.py:184](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L184)
- [backend/main.py:251](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L251)
- [backend/main.py:303](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L303)
现在写法是:
```python
return JSONResponse(content={"error": str(e)}, status_code=500)
```
### 错误原因
这是开发期为了调试方便常见的写法。
但线上不应该把真实异常直接发给浏览器。
### 会导致什么
- 暴露内部实现细节
- 暴露依赖报错、路径、上游信息
### 整改方式
统一改成:
1. 前端只收到固定错误码和通用提示
2. 后端日志里保留详细异常
3. 返回 request id 方便排查
### 建议响应格式
```json
{
"error": {
"code": "UPSTREAM_TIMEOUT",
"message": "Service temporarily unavailable",
"request_id": "xxxx"
}
}
```
### 验收标准
- 前端不再收到 Python 原始报错
- 日志可通过 request id 查到真实错误
---
## 5. `/v1/convert` 和 `/v1/ocr` 没有文件安全边界
### 具体问题
- [backend/main.py:239](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L239)
- [backend/main.py:268](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L268)
- [backend/main.py:275](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L275)
- [backend/main.py:281](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L281)
- [src/utils/convert.js:22](/C:/Users/ydy/Desktop/llm-in-text/src/utils/convert.js#L22)
现在的问题是:
- 直接 base64 解码
- 没有严格文件大小限制
- 没有严格文件类型白名单
- 没有魔数校验
- 没有超时和并发保护
### 错误原因
当前实现是功能优先,默认相信前端传来的内容。
但上传链路是最容易出问题的地方之一。
### 会导致什么
- 大文件压垮内存
- 恶意文件拖慢 CPU
- 第三方库处理异常文件时出故障
### 整改方式
#### 对 `ocr`
1. 图片大小上限先改为 5MB 或 10MB
2. 只允许 `jpg/png/webp`
3. 服务端校验 MIME 和魔数
4. 增加请求超时
5. 增加并发限制
#### 对 `convert`
1. 只允许明确白名单格式
2. 每种格式单独设大小上限
3. 服务端检查扩展名和文件头
4. `markitdown` 执行增加超时
5. 临时文件放到独立目录
6. 临时文件异常时也要清理
### 建议白名单
- `.pdf`
- `.docx`
- `.pptx`
- `.xlsx`
- `.md`
- `.txt`
### 建议直接拒绝
- 可执行文件
- 压缩包
- 未知二进制
- 超大图片
### 验收标准
- 超限文件返回 413
- 非法类型返回 415
- OCR/convert 高并发下不会拖垮服务
---
## 6. 没有限流,匿名站点很容易被刷
### 具体问题
- 当前代码里没有 rate limit
- 没有按 IP、UA、路径、时间窗做限制
- `ACTIVE_COMPLETIONS` 只处理取消,不是限流器。[backend/main.py:29](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py#L29)
### 错误原因
因为现在的代码默认是“正常用户正常使用”。
但匿名公网站点上线后,必须假设会被脚本反复调用。
### 会导致什么
- 模型成本失控
- CPU、内存、连接数被耗尽
- 服务变慢甚至不可用
### 整改方式
匿名站点不做登录,标准保护方式是限流:
1. 反向代理层限流
2. 应用层限流
3. 高成本接口单独限流
### 建议策略
#### `/v1/completions`
- 单 IP 每分钟 20 到 60 次
- 同时进行中的请求数限制 2 到 4 个
#### `/v1/ocr`
- 单 IP 每分钟 5 到 10 次
- 同时进行中的 OCR 限制更低
#### `/v1/convert`
- 单 IP 每分钟 3 到 5 次
- 强并发限制 1 到 2
### 还可以加什么
- Cloudflare Turnstile / hCaptcha 这类站点级防刷
- 对明显机器人流量加 challenge
这不需要登录,也不需要用户体系。
### 验收标准
- 连续脚本请求会命中 429
- 单个来源无法无限刷接口
---
## 7. 模型调用没有明确超时与失败策略
### 具体问题
- [backend/llm.py:15](/C:/Users/ydy/Desktop/llm-in-text/backend/llm.py#L15)
当前 `ollama.AsyncClient` 调用没有明显的统一超时和失败分类。
### 错误原因
开发阶段一般默认上游会正常返回。
但生产里,上游模型服务经常会出现:
- 变慢
- 卡住
- 连接失败
- 超时
### 会导致什么
- 请求挂很久
- 连接被占住
- 用户看起来像页面没响应
### 整改方式
1. completions 设置明确超时,例如 15 到 30 秒
2. OCR 设置更短或更明确的处理时限
3. convert 设置文件转换超时
4. 把错误分成:
- timeout
- unavailable
- bad response
5. 前端对这些错误做不同提示
### 验收标准
- 上游模型挂掉时,接口会快速失败而不是一直卡住
---
## 8. 缺少健康检查接口
### 具体问题
当前没有明确的:
- `/health/live`
- `/health/ready`
### 错误原因
项目还是开发态,没有进入正式部署思路。
### 会导致什么
- 你很难判断服务是否真的可用
- 容器/进程平台无法正确探活
### 整改方式
增加两个接口:
#### `/health/live`
只表示“应用进程活着”
#### `/health/ready`
表示“应用准备好服务请求”
这个接口至少检查:
- 模型上游是否可连接
- 关键配置是否存在
### 验收标准
- 反向代理或容器平台能用它判断是否接流量
---
## 9. 预览组件有 XSS 风险
### 具体问题
- [src/components/MarkdownPreview.vue:2](/C:/Users/ydy/Desktop/llm-in-text/src/components/MarkdownPreview.vue#L2)
- [src/components/MarkdownPreview.vue:19](/C:/Users/ydy/Desktop/llm-in-text/src/components/MarkdownPreview.vue#L19)
现在做法是:
- `v-html`
- `html: true`
### 错误原因
这意味着 markdown 里的原始 HTML 会被直接渲染。
如果内容来源不完全可信,这就是典型 XSS 入口。
### 会导致什么
- 恶意脚本执行
- 页面被注入恶意 DOM
### 整改方式
你有两个选择:
#### 方案 A:最简单,直接关掉 HTML
把:
```js
html: true
```
改成:
```js
html: false
```
#### 方案 B:保留 HTML,但做净化
1. 引入 DOMPurify
2. `md.render()` 后先 sanitize
3. 再给 `v-html`
### 推荐
如果你不是必须支持原始 HTML,直接用方案 A。
### 验收标准
- 恶意 markdown/HTML 不会执行脚本
---
## 10. 前端默认配置会误连固定服务地址
### 具体问题
- [src/utils/config.js:1](/C:/Users/ydy/Desktop/llm-in-text/src/utils/config.js#L1)
- [.env.example:1](/C:/Users/ydy/Desktop/llm-in-text/.env.example#L1)
- [backend/llm.py:12](/C:/Users/ydy/Desktop/llm-in-text/backend/llm.py#L12)
默认值里有固定公网域名和固定内网 IP。
### 错误原因
这是把“某次部署环境”写成了“代码默认值”。
### 会导致什么
- 本地开发可能误连生产或旧环境
- 新服务器部署时容易配错
### 整改方式
1. 默认值改成本地开发地址或空值
2. 关键配置不存在时直接报错
3. `.env.example` 只放模板,不放真实地址
### 建议
- `VITE_API_BASE_URL` 默认走同域,如 `''`
- 前端优先使用 `/v1/...` 反代
- 后端 `OLLAMA_HOST` 必须来自环境变量
### 验收标准
- 不配置环境变量时,不会误连旧服务
---
## 11. 包版本和界面版本不一致
### 具体问题
- [package.json:4](/C:/Users/ydy/Desktop/llm-in-text/package.json#L4) 是 `0.0.0`
- [src/components/SettingsPanel.vue:275](/C:/Users/ydy/Desktop/llm-in-text/src/components/SettingsPanel.vue#L275) 写的是 `v0.1.0-beta`
### 错误原因
一个是包元数据,一个是手写展示文案,没人保证同步。
### 会导致什么
- 发布后你都不确定线上到底是哪版
### 整改方式
1. 统一从 `package.json` 注入版本
2. 前端不要手写版本号
### 验收标准
- 页面显示版本和构建版本完全一致
---
## 12. `package.json` 缺少质量脚本
### 具体问题
- [package.json:6](/C:/Users/ydy/Desktop/llm-in-text/package.json#L6)
当前只有:
- `dev`
- `build`
- `preview`
没有:
- `test`
- `lint`
- `check`
### 错误原因
项目还停留在“能运行”的阶段,没有建立质量门禁。
### 会导致什么
- 任何改动都只能靠手工试
- 回归问题容易漏
### 整改方式
至少补这些脚本:
```json
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "pytest backend/tests -q",
"lint": "eslint src",
"check": "npm run lint && npm run build && pytest backend/tests -q"
}
```
如果前端暂时没配 ESLint,也至少先把 `test``check` 建起来。
### 验收标准
- 以后每次改代码前后都能统一执行 `check`
---
## 13. 测试覆盖不够,缺关键路径
### 具体问题
当前测试主要是:
- prompt 构造
- 取消逻辑
- LLM 消息结构
缺少:
- 匿名访问基本流程
- 错误响应格式
- OCR 文件限制
- convert 文件限制
- 限流
- XSS
### 错误原因
现有测试更偏功能开发时的局部验证,不是上线前测试矩阵。
### 整改方式
补这些测试:
1. completions 正常返回
2. completions 上游超时
3. completions 请求超长
4. OCR 非法类型
5. OCR 超大图片
6. convert 非法类型
7. convert 超大文件
8. 限流命中
9. 未授权方案移除后,匿名访问可正常工作
10. markdown 预览 XSS 样例
### 验收标准
- 关键错误分支都有自动化测试
---
## 14. Service Worker 缓存策略还不够稳
### 具体问题
- [src/main.js:13](/C:/Users/ydy/Desktop/llm-in-text/src/main.js#L13)
- [public/sw.js:1](/C:/Users/ydy/Desktop/llm-in-text/public/sw.js#L1)
当前是手写缓存逻辑,版本固定写死。
### 错误原因
这是一个能用的基础实现,但不适合长期生产维护。
### 会导致什么
- 更新后可能缓存混乱
- 老版本资源残留
### 整改方式
如果你不强依赖离线能力:
1. 先临时关闭 SW
2. 等核心功能稳定后再重做 PWA
如果要保留:
1. 用成熟方案接管,如 Vite PWA / Workbox
2. 资源按 hash 控制
3. 做更新提示
### 推荐
如果现在重点是先上线稳定版,先停掉 SW 更省事。
### 验收标准
- 用户刷新后不会出现随机旧资源
---
## 15. 构建体积偏大
### 具体问题
本次构建已经出现大 chunk 警告,尤其是 Mermaid 相关包比较重。
### 错误原因
图表、编辑器、语法高亮、数学渲染这类库本身就大。
现在又没有足够按功能懒加载。
### 会导致什么
- 首屏慢
- 弱网体验差
### 整改方式
1. Mermaid 按需加载
2. 预览按需加载
3. OCR / convert 相关 UI 按需加载
4. 收敛 `manualChunks`
### 验收标准
- 首页首次加载明显更轻
---
## 16. 匿名站点应该怎么做保护,而不是登录
这是你这个项目最关键的方向问题。
你不想做用户级网站,这完全可以。
那就按匿名站点的标准做:
### 必做
1. 去掉前端共享密钥
2. 收紧 CORS
3. 加 Nginx / Cloudflare / 网关限流
4. 应用层再做限流
5. 限制请求体大小
6. 限制 OCR/convert 并发
7. 错误信息脱敏
8. 加健康检查
9. 处理 XSS
### 可选
1. Cloudflare Turnstile
2. 简单的人机验证 challenge
3. 对高频匿名流量启用冷却时间
### 不必做
1. 登录
2. 注册
3. 用户系统
4. JWT
只要你的目标是匿名工具站,而不是多租户平台,上面这套就够了。
---
## 最简修复顺序
如果你要最低成本把项目拉到“能较安全公开上线”的程度,建议顺序是:
1. 删除前端 API key 和后端固定 key 校验
2. 删除 IP 获取和地理位置推断
3. 收紧 CORS
4. 统一错误响应
5. 给 OCR/convert 加大小、类型、超时限制
6. 加限流
7. 修掉 `MarkdownPreview` 的 XSS 风险
8. 增加 `/health/live``/health/ready`
9.`test` / `check` 脚本
10. 视情况先关闭 service worker
---
## 这份清单对应的文件
- [backend/main.py](/C:/Users/ydy/Desktop/llm-in-text/backend/main.py)
- [backend/llm.py](/C:/Users/ydy/Desktop/llm-in-text/backend/llm.py)
- [src/utils/api.js](/C:/Users/ydy/Desktop/llm-in-text/src/utils/api.js)
- [src/utils/convert.js](/C:/Users/ydy/Desktop/llm-in-text/src/utils/convert.js)
- [src/components/MarkdownPreview.vue](/C:/Users/ydy/Desktop/llm-in-text/src/components/MarkdownPreview.vue)
- [src/stores/settings.js](/C:/Users/ydy/Desktop/llm-in-text/src/stores/settings.js)
- [src/components/SettingsPanel.vue](/C:/Users/ydy/Desktop/llm-in-text/src/components/SettingsPanel.vue)
- [public/sw.js](/C:/Users/ydy/Desktop/llm-in-text/public/sw.js)
- [package.json](/C:/Users/ydy/Desktop/llm-in-text/package.json)
+579
View File
@@ -0,0 +1,579 @@
# llm-in-text 生产环境修复清单
## 文档目的
本文档是针对当前仓库在 2026-04-01 状态下基于代码的生产就绪性审查。
它回答三个问题:
1. 当前项目是否已准备好投入生产?
2. 自上次审查以来已修复了哪些问题?
3. 还有什么因素阻碍安全上线?
本次审查仅限于仓库中已有的内容和本地直接验证的内容,不包括外部基础设施、反向代理配置、云资源、CI 平台密钥或运行时运维的完整审计。
## 审查范围
- 前端构建和运行时入口
- 后端 FastAPI 端点和模型集成
- 补全、OCR 和文件转换请求路径
- 隐私相关行为和本地存储
- 基础测试覆盖率和构建验证
- PWA/Service Worker 状态
- 仓库中存在的部署和运维工件
## 已验证的事实
以下项目已针对当前仓库直接验证:
- 前端生产构建通过 `npm.cmd run build` 成功
- 后端测试通过 `pytest backend/tests -q`
- 当前后端测试结果:`8 passed, 1 skipped`
- 存在健康检查端点:`/health/live``/health/ready`
- 前端不再硬编码 API 密钥
- 后端不再需要旧的 `X-API-Key`
- 前端隐私模式现在默认为启用
- 后端错误响应现在已规范化,不再返回原始异常字符串
- OCR 和转换端点现在强制执行基本的文件大小和扩展名检查
## 当前评估
项目尚未准备好投入生产。
当前状态更接近于:
- 一个可用的原型
- 内部演示
- 具有部分加固的预发布候选版本
由于缺少几个核心生产基线,它尚未准备好面向互联网的生产使用:
- 真正的限流和并发保护
- 正式的部署工件和运行时拓扑
- CI/CD 和自动化质量门禁
- 结构化的可观测性和告警
- 更强的请求验证和更安全的文件处理隔离
- 前端自动化测试和端到端验证
## 已修复的问题
与之前的清单相比,以下项目不再作为阻碍因素:
### 已修复:硬编码的前端/后端共享 API 密钥
- `src/utils/api.js` 不再发送 `X-API-Key`
- `src/utils/convert.js` 不再发送 `X-API-Key`
- `backend/main.py` 不再强制执行旧的静态密钥
这消除了上次审查中最严重的问题之一。
遗留问题:
- `backend/tests/test_main_cancel.py` 仍然包含过时的 `X-API-Key` 头,但它们目前是无效的,表明测试假设已过时而非活动中的认证逻辑
### 已修复:危险的通配符 CORS 配置
当前后端 CORS 限制为:
- `http://localhost:5173`
- `http://localhost:3000`
并使用:
- `allow_credentials=False`
- `allow_methods=["POST", "OPTIONS"]`
- `allow_headers=["Content-Type", "X-Request-Id"]`
这比之前的通配符配置安全得多。
遗留问题:
- CORS 仍然针对本地开发硬编码,对于 staging/生产环境不是环境驱动的
### 已修复:默认收集前端公网 IP
- `src/stores/settings.js` 现在将 `privacyMode` 默认为 `true`
- `src/utils/api.js` 不再调用 `ipify`
- `src/utils/api.js` 不再发送 `X-Client-IP`
- `backend/main.py` 不再将 IP 派生的位置注入提示词
遗留问题:
- `backend/geoip.py` 和 GeoLite 数据库仍然存在于仓库中,这可能会造成对当前隐私模型的混淆
### 已修复:向客户端暴露原始异常字符串
当前后端响应使用 `_error_response(...)` 和结构化载荷,例如:
- `INTERNAL_ERROR`
- `OCR_FAILED`
- `CONVERT_FAILED`
- `FILE_TOO_LARGE`
- `INVALID_FILE_TYPE`
这比直接返回 `str(exception)` 更好。
遗留问题:
- 日志仍然记录用户派生内容的预览,这是另一个隐私/可观测性问题
### 部分修复:上传和转换输入边界
`backend/main.py` 中的当前后端保护包括:
- OCR 大小上限:10 MB
- 转换大小上限:50 MB
- OCR 和转换的扩展名白名单
-`finally` 中清理临时转换文件
这是有意义的进展,但不足以投入生产。
## 生产阻碍因素
优先级含义:
- `P0`:必须在生产上线前完成
- `P1`:应在公开发布或广泛推广前完成
- `P2`:重要的后续加固和维护工作
---
## P0 阻碍因素
### P0-01 声明了限流和并发控制但未强制执行
当前状态:
- `backend/main.py` 定义了 `MAX_CONCURRENT_COMPLETIONS = 4`
- `backend/main.py` 定义了 `COMPLETION_RATE_LIMIT = 60`
- 没有任何实际的限流器使用这两个值
- OCR 和转换端点也没有真正的每客户端节流
风险:
- 容易滥用昂贵的补全/OCR/转换端点
- 可避免地对模型主机、CPU、内存和临时存储造成过载
- 突发流量下无法控制降级
必需的修复:
1. 在应用或网关中强制执行真正的每路由限流
2. 为补全作业添加真正的并发保护
3.`/v1/completions``/v1/ocr``/v1/convert` 添加单独预算
4. 达到限制时返回明确的 `429`
5. 为节流的请求和队列深度发出指标
验收标准:
- 重复的突发流量触发确定性的 `429`
- 并发补全不能超过配置的预算
- 负载测试下服务保持稳定
---
### P0-02 仓库中不存在生产部署基线
当前状态:
- 后端仅通过 `uvicorn.run(...)` 暴露开发式启动
- 没有 `Dockerfile`
- 没有 compose 文件
- 没有 Kubernetes 清单或 Helm chart
- 没有 systemd 单元
- 没有反向代理参考配置
- 没有记录的生产环境契约
风险:
- 没有可复现的部署路径
- 没有明确的过程监督、重启或优雅的发布模型
- 没有记录的 ingress/请求体大小/超时/TLS 姿态
必需的修复:
1. 定义一个官方部署目标
2. 为该目标添加部署工件
3. 记录所需的环境变量、端口、探针和存储
4. 定义优雅关闭和发布行为
5. 记录反向代理限制和信任边界
验收标准:
- 新环境可以从仓库文档和工件部署
- 健康探针已接入所选运行时
- 回滚路径已记录
---
### P0-03 缺少 CI/CD 和仓库质量门禁
当前状态:
- 仓库级别没有找到 `.github/workflows`
- `package.json` 没有真正的前端测试脚本
- 没有 lint 脚本
- 没有类型检查脚本
- 没有依赖扫描或密钥扫描工作流
风险:
- 回归只能手动捕获
- 安全和打包漂移很可能发生
- 生产就绪性取决于本地开发者的规范
必需的修复:
1. 为构建和测试添加 CI 工作流
2. 添加前端自动化测试
3. 在适用的情况下添加 lint 和类型检查门禁
4. 添加依赖漏洞扫描
5. 添加密钥扫描和基本 SAST
验收标准:
- 每个 PR 都运行构建、后端测试、前端测试和 lint
- 失败的检查阻止合并
---
### P0-04 文件处理路径仍然缺乏生产级隔离
当前状态:
- 后端将完整 base64 载荷解码到内存中
- 转换写入临时文件并将其传递给 `markitdown`
- OCR 和转换主要依赖扩展名检查,而不是内容嗅探
- 没有工作进程隔离或用于转换的单独沙箱
- 转换任务没有队列或资源预算
风险:
- 大型或并发上传导致内存峰值
- 畸形或对抗性文档会给解析器带来压力
- 转换工作负载可能干扰核心补全可用性
必需的修复:
1. 明确验证 base64 解码失败
2. 添加 MIME/内容嗅探,而不仅仅是扩展名检查
3. 在入口和应用层添加更低的、路由特定的请求体限制
4. 将转换隔离到单独的工作进程/进程边界
5. 添加超时、并发上限和队列深度控制
验收标准:
- 畸形载荷失败并返回明确的 4xx 响应
- 转换不能饿死补全服务
- 压力下临时文件和内存增长保持有界
---
### P0-05 环境配置不一致且不安全
当前状态:
- 前端 `.env.example` 相当安全
- 后端 `.env.example` 过时且与代码不一致
- 代码读取 `OLLAMA_HOST`
- 后端示例仍然使用 `OLLAMA_BASE_URL`
- 后端示例仍然定义 `OPENAI_API_KEY=ollama`,这是误导性的
风险:
- 新环境配置不正确
- 运营商可能假设不支持的认证/配置行为
- staging/生产漂移很可能发生
必需的修复:
1. 用代码实际使用的变量替换后端环境示例
2. 在启动时验证所需的环境变量
3. 分离开发、staging 和生产环境契约
4. 缺失关键配置时快速失败
验收标准:
- 示例环境文件匹配真实运行时行为
- 无效或缺失关键配置导致启动失败
---
## P1 高优先级差距
### P1-01 日志仍然捕获用户派生内容预览
当前状态:
- `backend/main.py` 记录提示词派生的前缀和后缀预览
- 补全结果记录包含内容预览
- OCR 和转换记录文本预览长度和片段
风险:
- 日志可能包含敏感文档内容
- 隐私姿态与应用可见的隐私设置不一致
- 难以证明保留/合规姿态
必需的修复:
1. 默认情况下停止记录用户内容正文和预览
2. 仅保留请求元数据:路由、请求 ID、状态、延迟、大小
3. 引入结构化 JSON 日志
4. 脱敏或哈希任何敏感标识符
验收标准:
- 默认日志不包含用户文档文本
- 请求关联仍可通过请求 ID 和元数据工作
### P1-02 请求验证仍然过于宽松
当前状态:
- Pydantic 模型定义了字段但没有长度或枚举约束
- `prefix``suffix``filename``reason` 受到极小约束
- base64 字段在路由特定检查之前仍然可能非常大
- 前端转换路径在将完整文件读入 base64 之前不执行预验证
风险:
- 过大或畸形的请求太容易到达昂贵的逻辑
- 端点之间的 4xx 行为不一致
必需的修复:
1. 为 Pydantic 字段添加长度和枚举约束
2. 明确验证 base64 格式
3. 更防御性地规范化文件名处理
4. 添加前端预检查大小/类型作为 UX
验收标准:
- 畸形请求尽早失败并返回确定性的 4xx 响应
### P1-03 前端自动化覆盖率基本缺失
当前状态:
- 后端有针对性的单元/集成风格测试
- 前端没有配置测试运行器
- 核心用户路径没有 E2E 覆盖率
重要说明:
- `backend/tests/test_main_cancel.py` 仍然发送过时的 `X-API-Key` 头;测试通过仅仅是因为后端忽略它们
风险:
- 编辑器、上传、OCR、转换和设置的回归将会遗漏
必需的修复:
1. 添加前端单元/组件测试
2. 为补全、取消、上传、OCR 和转换添加 E2E 覆盖率
3. 从后端测试中移除过时的认证假设
验收标准:
- 核心用户旅程在 CI 中自动覆盖
### P1-04 健康检查端点存在,但就绪性浅且缺少可观测性
当前状态:
- `/health/live` 存在
- `/health/ready` 存在
- 就绪性实际上不检查上游模型可用性
- 没有指标端点
- 没有追踪
- 没有告警定义
风险:
- 运行时故障检测太晚
- 平台探针可能报告健康而上游依赖不可用
必需的修复:
1. 使就绪性反映关键依赖状态
2. 添加请求/延迟/错误指标
3. 为上游故障和饱和添加告警阈值
4. 为关键路由定义仪表板
验收标准:
- 运营商可以快速检测模型依赖故障
- 请求成功率和延迟可观测
### P1-05 Service Worker 实现存在但被禁用
当前状态:
- `public/sw.js` 存在
- `src/main.js``&& false` 硬禁用注册
- Service Worker 策略是手写的并通过静态缓存名称版本化
风险:
- 当前仓库包含未被实际使用的休眠 PWA 逻辑
- 如果随意重新启用,更新和缓存行为可能很脆弱
必需的修复:
1. 确定 PWA 是否在生产范围内
2. 如果是,采用维护的策略如 Vite PWA/Workbox
3. 如果不是,移除无用的 Service Worker 代码以减少混淆
验收标准:
- PWA 行为要么被有意支持和测试,要么被完全移除
### P1-06 背景图像持久化可能导致本地存储和内存膨胀
当前状态:
- 设置面板将上传的背景图像读取为 data URL
- 背景图像数据存储在 localStorage 中
- 没有对背景资产强制执行明确的大小上限
风险:
- 存储配额耗尽
- 大图像导致 UI 缓慢
- 跨浏览器的持久化行为脆弱
必需的修复:
1. 读取前在客户端添加大小上限
2. 持久化前调整大小/压缩
3. 对于较大的资产,优先使用 blob/object URL 或 IndexedDB
4. 为存储溢出添加迁移/错误处理
验收标准:
- 大图像不能降低启动或破坏设置持久化
---
## P2 重要后续工作
### P2-01 构建成功,但 bundle/chunk 策略仍然粗糙
验证的构建输出显示:
- `manualChunks` 生成许多空 chunk
- 一个与 Mermaid 相关的大型 chunk 超过 1 MB 压缩后
风险:
- 不必要的 chunk 开销
- 较弱的设备上冷启动较慢
必需的修复:
1. 简化 `manualChunks`
2. 延迟加载重型可选功能
3. 清理 chunk 后重新测量首次加载成本
### P2-02 OCR 缓存和图像哈希缓存没有明确的驱逐策略
当前状态:
- OCR 数据存储在内存中的 `Map`
- 哈希缓存也在内存中
- 没有 TTL
- 没有最大条目数
风险:
- 长时间会话会累积内存
必需的修复:
1. 添加 TTL 和条目边界
2. 如需要,暴露缓存指标用于调试
### P2-03 仓库仍然包含过时和混淆的工件
示例:
- `backend/geoip.py` 和 GeoLite DB 仍然存在,尽管 IP 地理定位在请求流程中不再活跃
- `backend/.env.example` 记录的变量不是代码使用的
- 后端测试仍然包含过时的 `X-API-Key`
风险:
- 未来维护者可能无意中重新引入已移除的行为
必需的修复:
1. 移除死代码和过时配置
2. 使测试和文档与当前实现保持一致
---
## 上线前必需的缺失证据
仓库目前不提供以下生产能力的证据:
- staging 部署管道
- 回滚程序
- 流量/负载测试结果
- 故障注入或混沌测试
- 备份/恢复程序
- 事件响应运行手册
- SLO/SLA 定义
- 安全扫描基线
- 依赖更新策略
- 隐私/数据保留文档
目前应将证据缺失视为未就绪,而不是隐式完成。
## 推荐的修复顺序
### 第一阶段:解除生产上线阻碍
1. 实现真正的限流和并发强制执行
2. 定义官方部署拓扑和工件
3. 添加 CI/CD 质量门禁
4. 加固和隔离文件处理工作负载
5. 修复后端环境配置契约
### 第二阶段:稳定运维和隐私姿态
1. 移除承载内容的日志
2. 加强请求验证
3. 深化就绪检查和指标
4. 添加前端和 E2E 自动化测试
### 第三阶段:性能和可维护性清理
1. 清理 chunk 策略
2. 限制 OCR/图像缓存
3. 移除过时代码和配置
4. 决定 PWA 支持是保留还是移除
## 最低上线门槛
至少在以下所有条件都满足之前,不应称该项目为生产就绪:
- 所有 `P0` 项目都已完成
- 日志不再捕获用户内容
- 前端和端到端自动化测试存在并在 CI 中运行
- 就绪性反映真实的上游依赖状态
- 部署和回滚已记录且可重现
- staging 环境已通过集成验证
- 至少执行了一次受控负载测试并经过审查
## 最终评估
与之前的清单相比,该项目已有实质性改进。几个严重的早期发现不再成立,特别是:
- 硬编码的认证密钥暴露
- 通配符式 CORS 姿态
- 默认公网 IP 收集
- 原始异常泄漏
然而,这一进展并不意味着已准备好投入生产。
当前仓库展示了有用的加固工作,但仍然缺乏生产服务预期的运维、测试、节流、部署和可观测性基线。
+6 -4
View File
@@ -59,7 +59,8 @@ llm-in-text/
│ │ └── index.ts # 插件导出 │ │ └── index.ts # 插件导出
│ ├── utils/ │ ├── utils/
│ │ ├── api.js # API 调用封装 │ │ ├── api.js # API 调用封装
│ │ ── config.js # 配置文件 │ │ ── config.js # 配置文件
│ │ └── ocrCache.js # OCR 缓存管理
│ ├── App.vue │ ├── App.vue
│ └── main.js │ └── main.js
├── backend/ ├── backend/
@@ -189,7 +190,7 @@ export const copilotGhostMark = $markSchema('copilot_ghost', () => ({
flowchart LR flowchart LR
A[用户输入] --> B{文档变化?} A[用户输入] --> B{文档变化?}
B -->|是| C[清除旧建议] B -->|是| C[清除旧建议]
C --> D[防抖 500ms] C --> D[防抖 1000ms]
D --> E[发送 API 请求] D --> E[发送 API 请求]
E --> F[收到建议] E --> F[收到建议]
F --> G[插入 Ghost Text] F --> G[插入 Ghost Text]
@@ -225,7 +226,7 @@ sequenceDiagram
U->>E: 输入文本 U->>E: 输入文本
E->>P: view.update() E->>P: view.update()
P->>P: 清除旧建议 P->>P: 清除旧建议
P->>P: 防抖 500ms P->>P: 防抖 1000ms
P->>A: fetchSuggestion(prefix, suffix) P->>A: fetchSuggestion(prefix, suffix)
A->>B: POST /v1/completions A->>B: POST /v1/completions
B->>B: build_prompt() B->>B: build_prompt()
@@ -254,9 +255,10 @@ sequenceDiagram
## 设计亮点 ## 设计亮点
1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析 1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析
2. **低延迟优化**:防抖机制 (500ms) + SSE 流式响应 + AbortController 取消过期请求 2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求
3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo 3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo
4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好 4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好
5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能
## 许可证 ## 许可证
-4
View File
@@ -1,4 +0,0 @@
OPENAI_API_KEY=ollama
OLLAMA_HOST=http://192.168.0.120:11434
OLLAMA_MODEL=gpt-oss:20b
VLM_MODEL=qwen3-vl:30b
+1 -1
View File
@@ -1,4 +1,4 @@
OPENAI_API_KEY=ollama OPENAI_API_KEY=ollama
OLLAMA_BASE_URL=http://192.168.0.120:11434/v1/ OLLAMA_BASE_URL=http://192.168.0.120:11434/v1/
OLLAMA_MODEL=gpt-oss:120b OLLAMA_MODEL=gpt-oss:20b
VLM_MODEL=qwen3-vl:30b VLM_MODEL=qwen3-vl:30b
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+56
View File
@@ -0,0 +1,56 @@
import os
import logging
from typing import Optional
logger = logging.getLogger("api")
_geoip_reader = None
def _get_reader():
global _geoip_reader
if _geoip_reader is not None:
return _geoip_reader
try:
import geoip2.database
db_path = os.path.join(os.path.dirname(__file__), "GeoLite2-City.mmdb")
if os.path.exists(db_path):
_geoip_reader = geoip2.database.Reader(db_path)
logger.info("GeoIP database loaded: %s", db_path)
return _geoip_reader
else:
logger.warning("GeoIP database not found: %s", db_path)
except ImportError:
logger.warning("geoip2 not installed, IP location disabled")
except Exception as e:
logger.warning("Failed to load GeoIP database: %s", e)
return None
def get_ip_location(ip: str) -> Optional[dict]:
if not ip or ip in ("127.0.0.1", "localhost", "::1"):
return None
reader = _get_reader()
if not reader:
return None
try:
response = reader.city(ip)
country = response.country.name
region = response.subdivisions.most_specific.name if response.subdivisions else None
city = response.city.name
parts = [p for p in [country, region, city] if p]
if not parts:
return None
return {
"country": country,
"region": region,
"city": city,
"display": " ".join(parts)
}
except Exception:
return None
def get_ip_location_text(ip: str) -> str:
loc = get_ip_location(ip)
return loc["display"] if loc else ""
+48 -10
View File
@@ -1,6 +1,7 @@
import os import os
import time import time
import logging import logging
import asyncio
from datetime import datetime from datetime import datetime
import ollama import ollama
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -8,9 +9,14 @@ from dotenv import load_dotenv
load_dotenv() load_dotenv()
OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gpt-oss:20b') OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gpt-oss:20b')
OLLAMA_HOST = os.getenv('OLLAMA_HOST', 'http://192.168.0.120:11434') OLLAMA_HOST = os.getenv('OLLAMA_HOST', 'http://localhost:11434')
VLM_MODEL = os.getenv('VLM_MODEL', 'qwen3-vl:30b') VLM_MODEL = os.getenv('VLM_MODEL', 'qwen3-vl:30b')
# Timeouts in seconds
COMPLETION_TIMEOUT = 30
OCR_TIMEOUT = 60
CONVERT_TIMEOUT = 30
client = ollama.AsyncClient(host=OLLAMA_HOST) client = ollama.AsyncClient(host=OLLAMA_HOST)
logger = logging.getLogger("llm") logger = logging.getLogger("llm")
@@ -54,31 +60,60 @@ def _extract_message(response) -> tuple[str, str]:
return content, thinking return content, thinking
async def call_ollama(prompt: str, *, tag: str = "default", temperature: float = 0.7) -> dict: async def call_ollama(
prompt: str,
*,
system_prompt: str = None,
tag: str = "default",
temperature: float = 0.7,
thinking: str = None,
) -> dict:
""" """
调用 Ollama API 并返回 content 和 thinking。 调用 Ollama API 并返回 content 和 thinking。
""" """
start = time.perf_counter() start = time.perf_counter()
start_dt = datetime.now() start_dt = datetime.now()
logger.info( logger.info(
"[LLM][%s] request model=%s host=%s prompt_chars=%d temp=%.2f", "[LLM][%s] request model=%s host=%s prompt_chars=%d system_chars=%d temp=%.2f thinking=%s",
tag, tag,
OLLAMA_MODEL, OLLAMA_MODEL,
OLLAMA_HOST, OLLAMA_HOST,
len(prompt), len(prompt),
len(system_prompt or ""),
temperature, temperature,
thinking,
) )
try: try:
response = await client.chat( messages = []
model=OLLAMA_MODEL, if system_prompt and system_prompt.strip():
messages=[{'role': 'user', 'content': prompt}], messages.append({"role": "system", "content": system_prompt})
stream=False, messages.append({"role": "user", "content": prompt})
options={
kwargs = {
"model": OLLAMA_MODEL,
"messages": messages,
"stream": False,
"options": {
'temperature': temperature, 'temperature': temperature,
'repeat_penalty': 1.1, 'repeat_penalty': 1.1,
}, },
}
if thinking:
kwargs["think"] = thinking
response = await asyncio.wait_for(client.chat(**kwargs), timeout=COMPLETION_TIMEOUT)
except asyncio.CancelledError:
elapsed_ms = (time.perf_counter() - start) * 1000
end_dt = datetime.now()
logger.info(
"[LLM][%s] call_time [%s --> %s]",
tag,
start_dt.strftime("%H:%M:%S"),
end_dt.strftime("%H:%M:%S"),
) )
logger.warning("[LLM][%s] request cancelled after %.1fms", tag, elapsed_ms)
raise
except Exception: except Exception:
elapsed_ms = (time.perf_counter() - start) * 1000 elapsed_ms = (time.perf_counter() - start) * 1000
end_dt = datetime.now() end_dt = datetime.now()
@@ -112,7 +147,7 @@ async def call_ollama(prompt: str, *, tag: str = "default", temperature: float =
if not content.strip(): if not content.strip():
logger.warning("[LLM][%s] empty content returned by model", tag) logger.warning("[LLM][%s] empty content returned by model", tag)
return {"content": content, "thinking": thinking} return {"content": content, "think": thinking}
async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str: async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str:
start = time.perf_counter() start = time.perf_counter()
@@ -126,7 +161,8 @@ async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str:
) )
try: try:
response = await client.chat( response = await asyncio.wait_for(
client.chat(
model=VLM_MODEL, model=VLM_MODEL,
messages=[{ messages=[{
'role': 'user', 'role': 'user',
@@ -135,6 +171,8 @@ async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str:
}], }],
stream=False, stream=False,
options={'temperature': 0.3} options={'temperature': 0.3}
),
timeout=OCR_TIMEOUT
) )
except Exception: except Exception:
elapsed_ms = (time.perf_counter() - start) * 1000 elapsed_ms = (time.perf_counter() - start) * 1000
+272 -34
View File
@@ -1,14 +1,20 @@
from fastapi import FastAPI import asyncio
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, JSONResponse
from pydantic import BaseModel
import json
import base64 import base64
import uuid import json
import logging import logging
import os
import tempfile
import uuid
from typing import Optional
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse, StreamingResponse
from pydantic import BaseModel
from prompt import build_prompt, prepare_prompt_context
from llm import call_ollama, call_vlm_ocr from llm import call_ollama, call_vlm_ocr
from prompt import build_completion_prompts, prepare_prompt_context
import markitdown
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@@ -20,21 +26,62 @@ app = FastAPI()
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=[
allow_credentials=True, "http://localhost:5173",
allow_methods=["*"], "http://localhost:3000",
allow_headers=["*"], "https://www.imageteach.tech",
"https://chat.imageteach.tech",
],
allow_credentials=False,
allow_methods=["POST", "OPTIONS"],
allow_headers=["Content-Type", "X-Request-Id"],
) )
ACTIVE_COMPLETIONS: dict[str, asyncio.Task] = {}
ACTIVE_COMPLETIONS_LOCK = asyncio.Lock()
# Rate limiting
MAX_CONCURRENT_COMPLETIONS = 4
COMPLETION_RATE_LIMIT = 60 # per minute
# File size limits (bytes)
MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10MB
MAX_CONVERT_SIZE = 50 * 1024 * 1024 # 50MB
# Allowed file extensions
ALLOWED_IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}
ALLOWED_CONVERT_EXTENSIONS = {".pdf", ".docx", ".pptx", ".xlsx", ".md", ".txt"}
class UserPreferences(BaseModel):
language: str = "auto"
currency: str = "auto"
timezone: str = "auto"
class CompletionRequest(BaseModel): class CompletionRequest(BaseModel):
prefix: str prefix: str
suffix: str suffix: str
languageId: str = 'markdown' languageId: str = "markdown"
model_thinking: str = "low"
privacy_mode: bool = False
user_preferences: Optional[UserPreferences] = None
class CancelCompletionRequest(BaseModel):
request_id: str
reason: str = "abort"
class OCRRequest(BaseModel): class OCRRequest(BaseModel):
image: str image: str
filename: str = "image.jpg" filename: str = "image.jpg"
language: str = 'auto' language: str = "auto"
class ConvertRequest(BaseModel):
file: str
filename: str = "document.pdf"
def _preview(text: str, limit: int = 80) -> str: def _preview(text: str, limit: int = 80) -> str:
@@ -43,44 +90,139 @@ def _preview(text: str, limit: int = 80) -> str:
return value return value
return value[:limit] + "..." return value[:limit] + "..."
def _error_response(request_id: str, code: str, message: str, status_code: int = 500) -> JSONResponse:
return JSONResponse(
content={
"error": {
"code": code,
"message": message,
"request_id": request_id,
}
},
status_code=status_code,
)
def _sse_payload(payload: dict) -> str:
return f"data: {json.dumps(payload)}\n\n"
@app.post("/v1/completions") @app.post("/v1/completions")
async def create_completion(request: CompletionRequest): async def create_completion(request: Request, req: CompletionRequest):
request_id = str(uuid.uuid4())[:8] request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4())
request_tag = request_id[:8]
inference_task: Optional[asyncio.Task] = None
try: try:
logger.info( logger.info(
"[%s] /v1/completions prefix_chars=%d suffix_chars=%d lang=%s prefix_tail='%s' suffix_head='%s'", "[%s] /v1/completions request_id=%s prefix_chars=%d suffix_chars=%d lang=%s thinking=%s privacy=%s",
request_tag,
request_id, request_id,
len(request.prefix or ""), len(req.prefix or ""),
len(request.suffix or ""), len(req.suffix or ""),
request.languageId, req.languageId,
_preview((request.prefix or "")[-120:]), req.model_thinking,
_preview((request.suffix or "")[:120]), req.privacy_mode,
) )
llm_prefix, llm_suffix = prepare_prompt_context(request.prefix or "", request.suffix or "")
logger.info("[%s] llm_input_prefix=%r", request_id, llm_prefix)
logger.info("[%s] llm_input_suffix=%r", request_id, llm_suffix)
prompt = build_prompt(request.prefix, request.suffix, request.languageId)
result = await call_ollama(prompt, tag=f"{request_id}-primary", temperature=0.7)
llm_prefix, llm_suffix = prepare_prompt_context(req.prefix or "", req.suffix or "")
logger.info("[%s] llm_input_prefix=%r", request_tag, llm_prefix)
logger.info("[%s] llm_input_suffix=%r", request_tag, llm_suffix)
system_prompt, user_prompt = build_completion_prompts(
req.prefix,
req.suffix,
req.languageId,
thinking_level=req.model_thinking,
preferences=req.user_preferences,
)
inference_task = asyncio.create_task(
call_ollama(
user_prompt,
system_prompt=system_prompt,
tag=f"{request_tag}-primary",
temperature=0.7,
thinking=req.model_thinking if req.model_thinking != "none" else None,
)
)
async with ACTIVE_COMPLETIONS_LOCK:
existing = ACTIVE_COMPLETIONS.get(request_id)
if existing and not existing.done():
existing.cancel()
ACTIVE_COMPLETIONS[request_id] = inference_task
result = await inference_task
content = result["content"] or "" content = result["content"] or ""
if not content.strip(): if not content.strip():
logger.warning("[%s] primary returned empty content, returning empty result", request_id) logger.warning("[%s] primary returned empty content, returning empty result", request_tag)
logger.info( logger.info(
"[%s] completion resolved source=primary content_chars=%d content_preview='%s'", "[%s] completion resolved source=primary request_id=%s content_chars=%d content_preview='%s'",
request_tag,
request_id, request_id,
len(content), len(content),
_preview(content, 120), _preview(content, 120),
) )
async def generate(): async def generate():
yield f"data: {json.dumps({'content': content})}\n\n" yield _sse_payload({"content": content})
yield f"data: {json.dumps({'done': True})}\n\n" yield _sse_payload({"done": True})
return StreamingResponse(generate(), media_type="text/event-stream") return StreamingResponse(generate(), media_type="text/event-stream")
except asyncio.CancelledError:
logger.info("[%s] /v1/completions cancelled request_id=%s", request_tag, request_id)
async def cancelled():
yield _sse_payload({"cancelled": True, "request_id": request_id, "done": True})
return StreamingResponse(cancelled(), media_type="text/event-stream")
except Exception as e: except Exception as e:
logger.exception("[%s] /v1/completions failed: %s", request_id, e) logger.exception("[%s] /v1/completions failed request_id=%s: %s", request_tag, request_id, e)
return JSONResponse(content={"error": str(e)}, status_code=500) return _error_response(request_id, "INTERNAL_ERROR", "Service temporarily unavailable", 500)
finally:
async with ACTIVE_COMPLETIONS_LOCK:
active = ACTIVE_COMPLETIONS.get(request_id)
if active is not None and active is inference_task:
ACTIVE_COMPLETIONS.pop(request_id, None)
@app.post("/v1/completions/cancel")
async def cancel_completion(req: CancelCompletionRequest):
request_tag = str(uuid.uuid4())[:8]
request_id = req.request_id or ""
async with ACTIVE_COMPLETIONS_LOCK:
task = ACTIVE_COMPLETIONS.get(request_id)
if task is None:
logger.info(
"[%s] /v1/completions/cancel request_id=%s status=not_found reason=%s",
request_tag,
request_id,
req.reason,
)
return {"cancelled": False, "status": "not_found"}
if task.done():
logger.info(
"[%s] /v1/completions/cancel request_id=%s status=already_done reason=%s",
request_tag,
request_id,
req.reason,
)
return {"cancelled": False, "status": "already_done"}
task.cancel()
logger.info(
"[%s] /v1/completions/cancel request_id=%s status=ok reason=%s",
request_tag,
request_id,
req.reason,
)
return {"cancelled": True, "status": "ok"}
@app.post("/v1/ocr") @app.post("/v1/ocr")
async def ocr_image(request: OCRRequest): async def ocr_image(request: OCRRequest):
@@ -93,7 +235,22 @@ async def ocr_image(request: OCRRequest):
request.language, request.language,
len(request.image or ""), len(request.image or ""),
) )
# Check file size before decoding
if len(request.image or "") > MAX_IMAGE_SIZE * 4 // 3: # base64 overhead
return _error_response(request_id, "FILE_TOO_LARGE", "Image exceeds 10MB limit", 413)
# Check extension
ext = os.path.splitext(request.filename)[1].lower()
if ext not in ALLOWED_IMAGE_EXTENSIONS:
return _error_response(request_id, "INVALID_FILE_TYPE", "Only jpg/png/webp allowed", 415)
image_bytes = base64.b64decode(request.image) image_bytes = base64.b64decode(request.image)
# Check actual decoded size
if len(image_bytes) > MAX_IMAGE_SIZE:
return _error_response(request_id, "FILE_TOO_LARGE", "Image exceeds 10MB limit", 413)
logger.info("[%s] /v1/ocr decoded image_bytes=%d", request_id, len(image_bytes)) logger.info("[%s] /v1/ocr decoded image_bytes=%d", request_id, len(image_bytes))
result = await call_vlm_ocr(image_bytes, request.language) result = await call_vlm_ocr(image_bytes, request.language)
logger.info( logger.info(
@@ -105,8 +262,89 @@ async def ocr_image(request: OCRRequest):
return {"text": result, "filename": request.filename} return {"text": result, "filename": request.filename}
except Exception as e: except Exception as e:
logger.exception("[%s] /v1/ocr failed: %s", request_id, e) logger.exception("[%s] /v1/ocr failed: %s", request_id, e)
return JSONResponse(content={"error": str(e)}, status_code=500) return _error_response(request_id, "OCR_FAILED", "Failed to process image", 500)
@app.post("/v1/convert")
async def convert_to_markdown(request: ConvertRequest):
"""将文件转换为Markdown格式"""
request_id = str(uuid.uuid4())[:8]
try:
logger.info(
"[%s] /v1/convert filename=%s file_base64_chars=%d",
request_id,
request.filename,
len(request.file or ""),
)
# Check file size before decoding
if len(request.file or "") > MAX_CONVERT_SIZE * 4 // 3:
return _error_response(request_id, "FILE_TOO_LARGE", "File exceeds 50MB limit", 413)
# Get file extension and validate
ext = os.path.splitext(request.filename)[1].lower()
if ext not in ALLOWED_CONVERT_EXTENSIONS:
return _error_response(request_id, "INVALID_FILE_TYPE", "Only pdf/docx/pptx/xlsx/md/txt allowed", 415)
# 解码Base64文件内容
file_bytes = base64.b64decode(request.file)
# Check actual decoded size
if len(file_bytes) > MAX_CONVERT_SIZE:
return _error_response(request_id, "FILE_TOO_LARGE", "File exceeds 50MB limit", 413)
logger.info("[%s] /v1/convert decoded file_bytes=%d", request_id, len(file_bytes))
# 创建临时文件
with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp:
tmp.write(file_bytes)
tmp_path = tmp.name
try:
# 使用MarkItDown转换为Markdown
md = markitdown.MarkItDown()
result = md.convert(tmp_path)
markdown_text = result.text_content
logger.info(
"[%s] /v1/convert success text_chars=%d text_preview='%s'",
request_id,
len(markdown_text or ""),
_preview(markdown_text, 120),
)
return {
"markdown": markdown_text,
"filename": request.filename
}
finally:
# 清理临时文件
if os.path.exists(tmp_path):
os.unlink(tmp_path)
except Exception as e:
logger.exception("[%s] /v1/convert failed: %s", request_id, e)
return _error_response(request_id, "CONVERT_FAILED", "Failed to convert file", 500)
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
uvicorn.run(app, host="0.0.0.0", port=8001)
@app.get("/health/live")
async def health_live():
return {"status": "ok"}
@app.get("/health/ready")
async def health_ready():
# Check if critical components are available
try:
# Could add more checks here (e.g., Ollama connectivity)
return {"status": "ready"}
except Exception as e:
logger.warning("[health/ready] not ready: %s", e)
return _error_response("health-check", "NOT_READY", "Service not ready", 503)
+569 -46
View File
@@ -1,5 +1,41 @@
from datetime import datetime, timedelta, timezone
import re
from typing import Tuple from typing import Tuple
def _get_current_datetime(timezone_pref: str = "auto") -> str:
# Default to UTC+8 if auto or not specified.
offset = 8
tz_info = " (UTC+8)"
if timezone_pref and timezone_pref != "auto":
# Parse values like "UTC+8" or "GMT-5".
match = re.search(r"([+-])(\d+)", timezone_pref)
if match:
sign = match.group(1)
hours = int(match.group(2))
offset = hours if sign == "+" else -hours
tz_info = f" ({timezone_pref})"
else:
tz_info = f" ({timezone_pref})"
now = datetime.now(timezone(timedelta(hours=offset)))
weekdays = [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
]
weekday = weekdays[now.weekday()]
return (
f"{now.year}-{now.month:02d}-{now.day:02d} "
f"{weekday} {now.hour:02d}:{now.minute:02d}:{now.second:02d}{tz_info}"
)
def _sanitize_language_id(language_id: str) -> str: def _sanitize_language_id(language_id: str) -> str:
if not language_id: if not language_id:
return "markdown" return "markdown"
@@ -11,70 +47,537 @@ def _sanitize_language_id(language_id: str) -> str:
return value or "markdown" return value or "markdown"
def _normalize_newlines(text: str) -> str:
return (text or "").replace("\r\n", "\n").replace("\r", "\n")
def _prepare_context(prefix: str, suffix: str) -> Tuple[str, str]: def _prepare_context(prefix: str, suffix: str) -> Tuple[str, str]:
""" """
Prepare prefix/suffix for model completion context. Prepare prefix/suffix for model completion context.
Filter out potential web-scraping or legacy artifacts like <br>, <br/>, <br\\>.
""" """
return prefix, suffix br_pattern = re.compile(r"<br\s*/?\s*\\?>", re.IGNORECASE)
clean_prefix = br_pattern.sub("", prefix or "")
clean_suffix = br_pattern.sub("", suffix or "")
return clean_prefix, clean_suffix
FENCE_LINE_RE = re.compile(r"^[ \t]*```.*$")
FENCE_INFO_RE = re.compile(r"^[ \t]*```[ \t]*(.*)$")
MERMAID_CONTEXT_RE = re.compile(
r"```[ \t]*mermaid\b|"
r"\b(flowchart|sequencediagram|classdiagram|statediagram(?:-v2)?|"
r"erdiagram|journey|gantt|pie|mindmap|timeline|gitgraph|quadrantchart|xychart-beta)\b|"
r"\bgraph[ \t]+(TD|TB|BT|RL|LR)\b",
re.IGNORECASE,
)
def _cursor_in_fenced_code_block(prefix: str) -> bool:
"""
Determine whether the cursor is currently inside a fenced code block.
The state is computed by toggling on each markdown fence line that matches:
^[ \t]*```.*$
"""
return _active_fence_language(prefix) != "none"
def _active_fence_language(prefix: str) -> str:
"""
Return active fence language at cursor based on prefix.
- "none": cursor is outside fenced code block
- "unknown": cursor is inside a fence without language tag
- "<language>": cursor is inside a fenced block with language tag
"""
normalized = _normalize_newlines(prefix)
in_fence = False
active_language = "none"
for line in normalized.split("\n"):
if FENCE_LINE_RE.match(line):
if in_fence:
in_fence = False
active_language = "none"
else:
info_match = FENCE_INFO_RE.match(line)
info = info_match.group(1).strip() if info_match else ""
if not info:
active_language = "unknown"
else:
first_token = info.split()[0]
lang_chars = []
for ch in first_token.strip():
if ch.isalnum() or ch in "-_+.":
lang_chars.append(ch)
active_language = "".join(lang_chars)[:32].lower() or "unknown"
in_fence = True
return active_language if in_fence else "none"
def _is_mermaid_context(prefix: str, suffix: str, cursor_fence_language: str) -> bool:
if cursor_fence_language == "mermaid":
return True
prefix_tail = (prefix or "")[-1200:]
suffix_head = (suffix or "")[:400]
combined = f"{prefix_tail}\n{suffix_head}"
return MERMAID_CONTEXT_RE.search(combined) is not None
def prepare_prompt_context(prefix: str, suffix: str) -> Tuple[str, str]: def prepare_prompt_context(prefix: str, suffix: str) -> Tuple[str, str]:
return _prepare_context(prefix, suffix) return _prepare_context(prefix, suffix)
def build_prompt(prefix: str, suffix: str, language_id: str = "markdown") -> str: LANGUAGE_SYNONYMS = {
safe_language_id = _sanitize_language_id(language_id) "md": "markdown",
recent_prefix, recent_suffix = _prepare_context(prefix, suffix) "markdown": "markdown",
"txt": "text",
"text": "text",
"plain": "text",
"plaintext": "text",
"py": "python",
"python": "python",
"js": "javascript",
"javascript": "javascript",
"jsx": "javascript",
"node": "javascript",
"ts": "typescript",
"tsx": "typescript",
"typescript": "typescript",
"json": "json",
"jsonc": "json",
"json5": "json",
"yaml": "yaml",
"yml": "yaml",
"toml": "toml",
"ini": "ini",
"cfg": "ini",
"bash": "bash",
"shell": "bash",
"sh": "bash",
"zsh": "bash",
"fish": "bash",
"ps": "powershell",
"ps1": "powershell",
"powershell": "powershell",
"sql": "sql",
"postgres": "sql",
"postgresql": "sql",
"mysql": "sql",
"sqlite": "sql",
"html": "html",
"xml": "xml",
"svg": "xml",
"css": "css",
"scss": "css",
"less": "css",
"latex": "latex",
"tex": "latex",
"katex": "latex",
"mermaid": "mermaid",
"c": "c",
"c++": "cpp",
"cpp": "cpp",
"cxx": "cpp",
"h": "c",
"hpp": "cpp",
"c#": "csharp",
"cs": "csharp",
"csharp": "csharp",
"go": "go",
"golang": "go",
"rust": "rust",
"rs": "rust",
"java": "java",
"kotlin": "kotlin",
"swift": "swift",
"ruby": "ruby",
"rb": "ruby",
"php": "php",
"lua": "lua",
"r": "r",
"matlab": "matlab",
"dart": "dart",
"docker": "dockerfile",
"dockerfile": "dockerfile",
"make": "makefile",
"makefile": "makefile",
"diff": "diff",
"patch": "diff",
"regex": "regex",
}
prompt = f"""You are an inline completion engine for a {safe_language_id} editor with ghost-text suggestions.
Your job: def _canonical_language_id(language_id: str) -> str:
- Return ONLY the text that should be inserted at the cursor between PREFIX and SUFFIX. safe = _sanitize_language_id(language_id).lower()
- Prefer a meaningful, non-empty insertion with moderate length. if not safe:
- Avoid overly short outputs with little information value. return "markdown"
return LANGUAGE_SYNONYMS.get(safe, safe)
Important context:
- PREFIX may contain OCR metadata inline after images, e.g. ![alt](url) <OCR:description>.
- The <OCR:...> is hidden context describing image content.
- Never copy, rewrite, or emit OCR tags in output.
- Never output <OCR: or >.
Hard rules: def _language_guidance(language_id: str) -> str:
1. Seamless join: canonical = _canonical_language_id(language_id)
PREFIX + OUTPUT + SUFFIX must read naturally as one continuous document. if canonical == "markdown":
2. No suffix repetition: return ""
Do NOT repeat text that already appears at the start of SUFFIX. if canonical == "mermaid":
3. Balanced length: return """
Prefer concise but meaningful continuation, not ultra-short fragments. Language-specific guidance (mermaid):
Default target is 20-120 characters and 1-3 lines for plain prose. - Output valid Mermaid syntax only.
You may be longer when structure requires it (lists, tables, code blocks, math blocks). - Prefer concise, syntactically correct diagram statements.
4. Avoid trivial output: - Avoid prose unless the user prompt explicitly requires it."""
Do not output only punctuation or filler such as ".", ",", ";", ":". if canonical == "latex":
Do not output just one token unless it is structurally necessary. return """
5. Preserve local style: Language-specific guidance (latex):
Match nearby language, tone, punctuation, spacing, and indentation. - Output LaTeX math content only when completing LaTeX.
6. Markdown awareness: - If CURSOR_IN_FENCED_CODE_BLOCK=true and CURSOR_FENCE_LANGUAGE is latex/tex/katex:
Continue active list/checkbox/ordered-list patterns when applicable. - Output raw LaTeX lines only.
Preserve indentation in nested list/code contexts. - Do not wrap with $ or $$."""
You may output full markdown structures when context needs them: headings, lists, tables, fenced code blocks, blockquotes, and LaTeX ($...$ / $$...$$). if canonical == "json":
Close obvious unclosed inline markdown markers only when needed to bridge. return """
7. Strict output format: Language-specific guidance (json):
Output insertion text only. - Output strict JSON only (no comments, no trailing commas).
No explanations, labels, or wrapper quotes around the whole output. - Ensure valid quotes and braces."""
Markdown syntax is allowed when it is the intended insertion (including fenced code blocks and LaTeX). if canonical == "yaml":
return """
Language-specific guidance (yaml):
- Output valid YAML only.
- Use consistent indentation and avoid tabs."""
if canonical == "toml":
return """
Language-specific guidance (toml):
- Output valid TOML only.
- Keep key types consistent."""
if canonical == "ini":
return """
Language-specific guidance (ini):
- Output valid INI only.
- Keep section headers and key=value pairs consistent."""
if canonical == "sql":
return """
Language-specific guidance (sql):
- Output a single, valid SQL statement unless context requires multiple.
- Prefer ANSI SQL when dialect is unclear."""
if canonical == "bash":
return """
Language-specific guidance (bash):
- Output POSIX-compatible shell when possible.
- Avoid interactive prompts or destructive commands unless requested."""
if canonical == "powershell":
return """
Language-specific guidance (powershell):
- Output valid PowerShell commands.
- Avoid destructive commands unless explicitly requested."""
if canonical == "html":
return """
Language-specific guidance (html):
- Output valid HTML only.
- Keep markup minimal and well-formed."""
if canonical == "css":
return """
Language-specific guidance (css):
- Output valid CSS only.
- Use concise, readable selectors."""
if canonical == "diff":
return """
Language-specific guidance (diff):
- Output a unified diff only.
- Ensure @@ hunk headers and +/- lines are consistent."""
if canonical == "regex":
return """
Language-specific guidance (regex):
- Output the regex pattern only.
- Avoid delimiters unless explicitly requested."""
if canonical in {"javascript", "typescript"}:
return f"""
Language-specific guidance ({canonical}):
- Output valid {canonical} code.
- Prefer modern syntax and avoid prose unless comments are needed."""
if canonical in {"python", "go", "rust", "java", "kotlin", "swift", "ruby", "php", "lua", "c", "cpp", "csharp", "r", "matlab", "dart"}:
return f"""
Language-specific guidance ({canonical}):
- Output valid {canonical} code.
- Avoid prose unless context clearly expects comments or docstrings."""
if canonical == "text":
return """
Language-specific guidance (text):
- Output plain text only.
- Avoid markdown formatting unless explicitly asked."""
if canonical == "xml":
return """
Language-specific guidance (xml):
- Output well-formed XML only.
- Ensure matching tags and proper escaping."""
if canonical == "dockerfile":
return """
Language-specific guidance (dockerfile):
- Output valid Dockerfile instructions only.
- Keep layers minimal and ordered logically."""
if canonical == "makefile":
return """
Language-specific guidance (makefile):
- Output valid Makefile syntax only.
- Use tabs for recipe lines."""
return f"""
Language-specific guidance ({canonical}):
- Output valid {canonical} code.
- Avoid prose unless context clearly expects comments or docstrings."""
Decision policy:
- If PREFIX already connects naturally to SUFFIX, add a brief but useful continuation when possible.
- If uncertain, prefer a complete short phrase or sentence with clear meaning.
Examples: def build_inline_system_prompt(language_id: str = "markdown") -> str:
safe_language_id = _canonical_language_id(language_id)
language_guidance = _language_guidance(safe_language_id)
system_prompt = f"""You are an inline completion engine for a {safe_language_id} editor with ghost-text suggestions.
Return only the insertion text that should be placed between PREFIX and SUFFIX.
Hard constraints you must follow:
1) Output-only contract:
- Output insertion text only.
- No explanations, no meta labels, no wrapper quotes around the whole answer.
2) Strict math formatting (KaTeX):
- If you output any math expression, it must be strict KaTeX-compatible math.
- Every formula must be wrapped with either $...$ (inline) or $$...$$ (block).
- Never output bare formulas without $ or $$ wrappers.
- Exception: If CURSOR_IN_FENCED_CODE_BLOCK=true and CURSOR_FENCE_LANGUAGE is latex/tex/katex,
output raw LaTeX without $ or $$ wrappers.
3) Strict code formatting:
- Read CURSOR_IN_FENCED_CODE_BLOCK from the user prompt.
- If CURSOR_IN_FENCED_CODE_BLOCK=true:
- You are already inside a fenced code block.
- Never output triple backticks.
- Output code lines only.
- If CURSOR_IN_FENCED_CODE_BLOCK=false:
- Any code output must be in a fenced code block with a language tag:
```{{language}}
...
```
- Do not output code snippets as inline backticks.
- Choose the language tag from context (no default fallback tag instruction).
4) Mermaid-specific completion rules:
- Read CURSOR_FENCE_LANGUAGE and MERMAID_CONTEXT from the user prompt.
- If CURSOR_FENCE_LANGUAGE=mermaid:
- Output Mermaid statements only.
- Never output triple backticks.
- Never output prose explanations.
- If CURSOR_IN_FENCED_CODE_BLOCK=false and MERMAID_CONTEXT=true:
- Output a complete Mermaid fenced block:
```mermaid
...
```
- Keep Mermaid syntax valid and concise.
- Never mix Mermaid code and explanatory narration in one output.
5) Boundary newline repair:
- Read PREFIX_ENDS_WITH_NEWLINE and SUFFIX_STARTS_WITH_NEWLINE from the user prompt.
- Carefully reason about whether OUTPUT should start or end with a newline.
- If PREFIX lacks a required boundary newline, add it at OUTPUT start.
- If SUFFIX lacks a required boundary newline, add it at OUTPUT end.
- Ensure PREFIX + OUTPUT + SUFFIX is structurally natural.
6) Context stitching:
- Do not repeat text that already appears at the start of SUFFIX.
- Preserve nearby language, tone, punctuation, indentation, and markdown structure.
- Continue existing structures naturally (lists, tables, block quotes, headings).
7) OCR safety:
- PREFIX may include hidden OCR metadata tags like <OCR:...>.
- Never output any OCR tag.
- Never output OCR tag fragments such as <OCR:...>."""
if language_guidance:
system_prompt = f"{system_prompt.rstrip()}\n{language_guidance.strip()}"
return system_prompt.strip()
INLINE_EXAMPLES = """[EX01] Prose continuation
<PREFIX>The quick brown fox </PREFIX> <PREFIX>The quick brown fox </PREFIX>
<SUFFIX>jumps over the lazy dog.</SUFFIX> <SUFFIX>jumps over the lazy dog.</SUFFIX>
Output: "moved quietly and then " Expected OUTPUT:
moved quietly and then
<PREFIX>## TODO\\n- [ ] Buy milk\\n- [ ] </PREFIX> [EX02] Avoid repeating suffix beginning
<PREFIX>Our launch plan starts with </PREFIX>
<SUFFIX>phase one, followed by phase two.</SUFFIX>
Expected OUTPUT:
careful internal testing before
[EX03] Continue markdown checklist
<PREFIX>## TODO
- [ ] Buy milk
- [ ] </PREFIX>
<SUFFIX></SUFFIX> <SUFFIX></SUFFIX>
Output: "Write release notes and share draft with team" Expected OUTPUT:
Write release notes and share draft with team
[EX04] Cursor outside code block, code must use fenced block
CURSOR_IN_FENCED_CODE_BLOCK=false
<PREFIX>Parse this JSON payload in Python:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
```python
import json
data = json.loads(payload)
```
[EX05] Cursor inside fenced code block, do not output fences
CURSOR_IN_FENCED_CODE_BLOCK=true
<PREFIX>```python
def add(a, b):
return </PREFIX>
<SUFFIX>
```</SUFFIX>
Expected OUTPUT:
a + b
[EX06] Inline math must use $...$
<PREFIX>The derivative of x^2 is </PREFIX>
<SUFFIX>.</SUFFIX>
Expected OUTPUT:
$2x$
[EX07] Block math must use $$...$$
<PREFIX>We can write the Gaussian integral as:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
$$
\\int_{-\\infty}^{\\infty} e^{-x^2}\\,dx = \\sqrt{\\pi}
$$
[EX08] Prefix misses boundary newline; add newline at output start
PREFIX_ENDS_WITH_NEWLINE=false
<PREFIX>Deployment steps:</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
- Build artifact
- Deploy service
[EX09] Suffix misses boundary newline; add newline at output end
SUFFIX_STARTS_WITH_NEWLINE=false
<PREFIX>Summary paragraph complete.</PREFIX>
<SUFFIX>## Next Section</SUFFIX>
Expected OUTPUT:
[EX10] OCR metadata exists but must never be emitted
<PREFIX>![whiteboard](img.png) <OCR:equation y = mx + b>
The relationship is </PREFIX>
<SUFFIX>.</SUFFIX>
Expected OUTPUT:
$y = mx + b$
[EX11] Continue markdown table with correct row shape
<PREFIX>| Name | Score |
| --- | --- |
| Alice | 92 |
| Bob | </PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
88 |
[EX12] Mixed text + math + code in one insertion
CURSOR_IN_FENCED_CODE_BLOCK=false
<PREFIX>Use the area formula and provide a tiny JS helper.</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
The area is $A = \\pi r^2$.
```javascript
const area = (r) => Math.PI * r * r;
```
[EX13] Cursor inside mermaid fence: no backticks, mermaid lines only
CURSOR_IN_FENCED_CODE_BLOCK=true
CURSOR_FENCE_LANGUAGE=mermaid
<PREFIX>```mermaid
flowchart TD
A[Start] --> </PREFIX>
<SUFFIX>
```</SUFFIX>
Expected OUTPUT:
B{Valid?}
B -->|Yes| C[Done]
[EX14] Mermaid context outside fence: return full mermaid block
CURSOR_IN_FENCED_CODE_BLOCK=false
MERMAID_CONTEXT=true
<PREFIX>Please provide a simple release pipeline diagram.</PREFIX>
<SUFFIX></SUFFIX>
Expected OUTPUT:
```mermaid
flowchart LR
Build --> Test --> Deploy
```"""
def build_completion_prompts(
prefix: str,
suffix: str,
language_id: str = "markdown",
location: str = "",
thinking_level: str = "low",
preferences: object = None,
) -> Tuple[str, str]:
safe_language_id = _canonical_language_id(language_id)
recent_prefix, recent_suffix = _prepare_context(prefix, suffix)
recent_prefix = _normalize_newlines(recent_prefix)
recent_suffix = _normalize_newlines(recent_suffix)
cursor_fence_language = _active_fence_language(recent_prefix)
cursor_in_fenced_code_block = cursor_fence_language != "none"
mermaid_context = _is_mermaid_context(
recent_prefix, recent_suffix, cursor_fence_language
)
prefix_ends_with_newline = recent_prefix.endswith("\n")
suffix_starts_with_newline = recent_suffix.startswith("\n")
tz_pref = preferences.timezone if preferences else "auto"
current_time = _get_current_datetime(tz_pref)
location_info = f"\nUser location: {location}" if location else ""
pref_info = []
if preferences:
if preferences.language and preferences.language != "auto":
pref_info.append(f"Preferred language: {preferences.language}")
if preferences.currency and preferences.currency != "auto":
pref_info.append(f"Preferred currency: {preferences.currency}")
preferences_instruction = "\n".join(pref_info)
if preferences_instruction:
preferences_instruction = f"\nUser Preferences:\n{preferences_instruction}"
user_prompt = f"""Current time: {current_time}{location_info}{preferences_instruction}
Reasoning hint: {thinking_level}
Editor language id: {safe_language_id}
Completion state flags:
- CURSOR_IN_FENCED_CODE_BLOCK: {"true" if cursor_in_fenced_code_block else "false"}
- CURSOR_FENCE_LANGUAGE: {cursor_fence_language}
- MERMAID_CONTEXT: {"true" if mermaid_context else "false"}
- PREFIX_ENDS_WITH_NEWLINE: {"true" if prefix_ends_with_newline else "false"}
- SUFFIX_STARTS_WITH_NEWLINE: {"true" if suffix_starts_with_newline else "false"}
Task:
- Produce the best insertion text at the cursor between PREFIX and SUFFIX.
- Keep insertion meaningful and non-empty.
- Keep insertion concise unless structure requires more content.
Context notes:
- PREFIX may include OCR metadata after image markdown, e.g. ![alt](url) <OCR:description>.
- OCR metadata is hidden context and must never be copied into output.
- Preserve local style and formatting.
Decision policy:
- Prioritize seamless join: PREFIX + OUTPUT + SUFFIX must read naturally.
- Do not repeat SUFFIX-leading text.
- If uncertain, prefer a complete short phrase/sentence with clear meaning.
Comprehensive examples:
{INLINE_EXAMPLES}
Now produce the insertion. Now produce the insertion.
@@ -88,7 +591,27 @@ Now produce the insertion.
Output:""" Output:"""
return prompt.strip() system_prompt = build_inline_system_prompt(safe_language_id)
return system_prompt.strip(), user_prompt.strip()
def build_prompt(
prefix: str,
suffix: str,
language_id: str = "markdown",
location: str = "",
thinking_level: str = "low",
preferences: object = None,
) -> str:
"""
Backward-compatible helper. Returns only the user prompt body.
"""
_, user_prompt = build_completion_prompts(
prefix=prefix,
suffix=suffix,
language_id=language_id,
location=location,
thinking_level=thinking_level,
preferences=preferences,
)
return user_prompt
+6
View File
@@ -4,3 +4,9 @@ ollama
pydantic pydantic
python-dotenv python-dotenv
httpx httpx
geoip2
markitdown[all]
python-docx
python-pptx
openpyxl
pypdf
+80
View File
@@ -0,0 +1,80 @@
"""
GeoIP2 IP归属地查询测试脚本
使用方法:
1. 安装依赖:pip install geoip2
2. 下载数据库:https://dev.maxmind.com/geoip/geoip2/geolite2/
3. 运行测试:python test_geoip.py
"""
import os
import sys
try:
import geoip2.database
except ImportError:
print("请先安装 geoip2: pip install geoip2")
sys.exit(1)
DB_PATH = os.path.join(os.path.dirname(__file__), "GeoLite2-City.mmdb")
TEST_IPS = [
"8.8.8.8", # Google DNS (美国)
"114.114.114.114", # 114 DNS (中国南京)
"223.5.5.5", # 阿里DNS (中国杭州)
"1.1.1.1", # Cloudflare DNS (澳大利亚)
"119.29.29.29", # 腾讯DNS (中国)
]
def get_location(reader, ip: str) -> dict:
try:
response = reader.city(ip)
return {
"ip": ip,
"country": response.country.name,
"country_code": response.country.iso_code,
"region": response.subdivisions.most_specific.name if response.subdivisions else None,
"city": response.city.name,
"latitude": response.location.latitude,
"longitude": response.location.longitude,
"timezone": response.location.time_zone,
}
except geoip2.errors.AddressNotFoundError:
return {"ip": ip, "error": "IP未在数据库中找到"}
except Exception as e:
return {"ip": ip, "error": str(e)}
def main():
if not os.path.exists(DB_PATH):
print(f"数据库文件不存在: {DB_PATH}")
print("请从 https://dev.maxmind.com/geoip/geoip2/geolite2/ 下载 GeoLite2-City.mmdb")
return
print(f"加载数据库: {DB_PATH}")
reader = geoip2.database.Reader(DB_PATH)
print("\n" + "=" * 60)
print("IP归属地查询测试")
print("=" * 60)
for ip in TEST_IPS:
result = get_location(reader, ip)
if "error" in result:
print(f"\n{ip}: {result['error']}")
else:
print(f"\n{ip}:")
print(f" 国家: {result['country']} ({result['country_code']})")
print(f" 地区: {result['region'] or '未知'}")
print(f" 城市: {result['city'] or '未知'}")
print(f" 坐标: {result['latitude']}, {result['longitude']}")
print(f" 时区: {result['timezone']}")
reader.close()
print("\n" + "=" * 60)
print("测试完成")
if __name__ == "__main__":
main()
+65
View File
@@ -0,0 +1,65 @@
import asyncio
import importlib
import sys
from pathlib import Path
import pytest
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
try:
llm = importlib.import_module("llm")
except ModuleNotFoundError:
pytest.skip("llm module dependencies are not available", allow_module_level=True)
def test_call_ollama_messages_roles_with_system(monkeypatch):
captured = {}
async def fake_chat(**kwargs):
captured["messages"] = kwargs["messages"]
return {"message": {"content": "ok", "thinking": ""}}
monkeypatch.setattr(llm.client, "chat", fake_chat)
result = asyncio.run(
llm.call_ollama(
"user prompt body",
system_prompt="system prompt body",
tag="test",
temperature=0.1,
)
)
assert result["content"] == "ok"
assert captured["messages"][0]["role"] == "system"
assert captured["messages"][0]["content"] == "system prompt body"
assert captured["messages"][1]["role"] == "user"
assert captured["messages"][1]["content"] == "user prompt body"
def test_call_ollama_messages_roles_without_system(monkeypatch):
captured = {}
async def fake_chat(**kwargs):
captured["messages"] = kwargs["messages"]
return {"message": {"content": "ok", "thinking": ""}}
monkeypatch.setattr(llm.client, "chat", fake_chat)
result = asyncio.run(
llm.call_ollama(
"user prompt only",
system_prompt="",
tag="test-no-system",
temperature=0.1,
)
)
assert result["content"] == "ok"
assert len(captured["messages"]) == 1
assert captured["messages"][0]["role"] == "user"
assert captured["messages"][0]["content"] == "user prompt only"
+118
View File
@@ -0,0 +1,118 @@
import asyncio
import importlib
import sys
import threading
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
try:
main = importlib.import_module("main")
except ModuleNotFoundError:
pytest.skip("main module dependencies are not available", allow_module_level=True)
API_KEY_HEADERS = {"X-API-Key": "your-secret-key-here"}
def _completion_payload():
return {
"prefix": "hello",
"suffix": "",
"languageId": "markdown",
"model_thinking": "low",
"privacy_mode": True,
}
def test_cancel_endpoint_cancels_running_task(monkeypatch):
main.ACTIVE_COMPLETIONS.clear()
started = threading.Event()
cancelled = threading.Event()
async def fake_call_ollama(*args, **kwargs):
started.set()
try:
while True:
await asyncio.sleep(0.05)
except asyncio.CancelledError:
cancelled.set()
raise
monkeypatch.setattr(main, "call_ollama", fake_call_ollama)
monkeypatch.setattr(main, "build_completion_prompts", lambda *a, **k: ("system", "user"))
monkeypatch.setattr(main, "prepare_prompt_context", lambda *a, **k: ("prefix", "suffix"))
with TestClient(main.app) as client:
request_id = "req-cancel-1"
completion_headers = {**API_KEY_HEADERS, "X-Request-Id": request_id}
response_box = {}
def send_completion():
response_box["response"] = client.post(
"/v1/completions",
headers=completion_headers,
json=_completion_payload(),
)
completion_thread = threading.Thread(target=send_completion, daemon=True)
completion_thread.start()
assert started.wait(timeout=2.0)
cancel_response = client.post(
"/v1/completions/cancel",
headers=API_KEY_HEADERS,
json={"request_id": request_id, "reason": "superseded"},
)
assert cancel_response.status_code == 200
assert cancel_response.json() == {"cancelled": True, "status": "ok"}
completion_thread.join(timeout=5.0)
assert not completion_thread.is_alive()
assert cancelled.wait(timeout=2.0)
completion_response = response_box["response"]
assert completion_response.status_code == 200
assert '"cancelled": true' in completion_response.text
def test_cancel_not_found():
main.ACTIVE_COMPLETIONS.clear()
with TestClient(main.app) as client:
response = client.post(
"/v1/completions/cancel",
headers=API_KEY_HEADERS,
json={"request_id": "missing", "reason": "abort"},
)
assert response.status_code == 200
assert response.json() == {"cancelled": False, "status": "not_found"}
def test_completion_normal_flow(monkeypatch):
main.ACTIVE_COMPLETIONS.clear()
async def fake_call_ollama(*args, **kwargs):
return {"content": "completion text", "think": ""}
monkeypatch.setattr(main, "call_ollama", fake_call_ollama)
monkeypatch.setattr(main, "build_completion_prompts", lambda *a, **k: ("system", "user"))
monkeypatch.setattr(main, "prepare_prompt_context", lambda *a, **k: ("prefix", "suffix"))
with TestClient(main.app) as client:
response = client.post(
"/v1/completions",
headers=API_KEY_HEADERS,
json=_completion_payload(),
)
assert response.status_code == 200
assert '"content": "completion text"' in response.text
assert '"done": true' in response.text
assert main.ACTIVE_COMPLETIONS == {}
+91
View File
@@ -0,0 +1,91 @@
import sys
from pathlib import Path
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
import prompt # noqa: E402
def test_prompt_builds_system_and_user():
system_prompt, user_prompt = prompt.build_completion_prompts(
prefix="The result is ",
suffix="for this dataset.",
language_id="markdown",
)
assert "Hard constraints you must follow" in system_prompt
assert "strict KaTeX-compatible math" in system_prompt
assert "$...$" in system_prompt
assert "$$...$$" in system_prompt
assert "```{language}" in system_prompt
assert "Mermaid-specific completion rules" in system_prompt
assert "CURSOR_FENCE_LANGUAGE" in system_prompt
assert "MERMAID_CONTEXT" in system_prompt
assert "Output Mermaid statements only." in system_prompt
assert "CURSOR_IN_FENCED_CODE_BLOCK" in user_prompt
assert "CURSOR_FENCE_LANGUAGE" in user_prompt
assert "MERMAID_CONTEXT" in user_prompt
assert "PREFIX_ENDS_WITH_NEWLINE" in user_prompt
assert "SUFFIX_STARTS_WITH_NEWLINE" in user_prompt
def test_cursor_in_fence_detection():
assert prompt._cursor_in_fenced_code_block("") is False
assert prompt._cursor_in_fenced_code_block("```python\nprint('x')\n") is True
assert prompt._cursor_in_fenced_code_block("```python\nprint('x')\n```\n") is False
assert prompt._cursor_in_fenced_code_block("text ```not-a-fence``` tail") is False
def test_active_fence_language_detection():
assert prompt._active_fence_language("") == "none"
assert prompt._active_fence_language("```mermaid\nflowchart TD\nA-->B\n") == "mermaid"
assert prompt._active_fence_language("```python\nprint('x')\n") == "python"
assert prompt._active_fence_language("```\nline\n") == "unknown"
assert prompt._active_fence_language("```mermaid\nA-->B\n```\n") == "none"
def test_newline_flags():
_, user_prompt_a = prompt.build_completion_prompts(
prefix="Hello",
suffix="World",
)
assert "CURSOR_IN_FENCED_CODE_BLOCK: false" in user_prompt_a
assert "CURSOR_FENCE_LANGUAGE: none" in user_prompt_a
assert "MERMAID_CONTEXT: false" in user_prompt_a
assert "PREFIX_ENDS_WITH_NEWLINE: false" in user_prompt_a
assert "SUFFIX_STARTS_WITH_NEWLINE: false" in user_prompt_a
_, user_prompt_b = prompt.build_completion_prompts(
prefix="Hello\n",
suffix="\nWorld",
)
assert "CURSOR_FENCE_LANGUAGE: none" in user_prompt_b
assert "PREFIX_ENDS_WITH_NEWLINE: true" in user_prompt_b
assert "SUFFIX_STARTS_WITH_NEWLINE: true" in user_prompt_b
def test_mermaid_context_flags():
_, prompt_in_mermaid = prompt.build_completion_prompts(
prefix="```mermaid\nflowchart TD\nA --> ",
suffix="\n```",
)
assert "CURSOR_IN_FENCED_CODE_BLOCK: true" in prompt_in_mermaid
assert "CURSOR_FENCE_LANGUAGE: mermaid" in prompt_in_mermaid
assert "MERMAID_CONTEXT: true" in prompt_in_mermaid
_, prompt_mermaid_keyword = prompt.build_completion_prompts(
prefix="Please draw a mermaid flowchart for deploy pipeline.",
suffix="",
)
assert "CURSOR_IN_FENCED_CODE_BLOCK: false" in prompt_mermaid_keyword
assert "CURSOR_FENCE_LANGUAGE: none" in prompt_mermaid_keyword
assert "MERMAID_CONTEXT: true" in prompt_mermaid_keyword
def test_examples_coverage():
_, user_prompt = prompt.build_completion_prompts(prefix="", suffix="")
for ex in range(1, 15):
assert f"[EX{ex:02d}]" in user_prompt
@@ -1,76 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITelemetryService, TelemetryEventMeasurements, TelemetryEventProperties } from '../../../../../platform/telemetry/common/telemetry';
import { wrapEventNameForPrefixRemoval } from '../../../../../platform/telemetry/node/azureInsightsReporter';
import { createServiceIdentifier } from '../../../../../util/common/services';
import { TelemetryMeasurements, TelemetryProperties, TelemetryStore } from '../../lib/src/telemetry';
import type { TelemetrySpy } from '../../lib/src/test/telemetrySpy';
export const ICompletionsTelemetryService = createServiceIdentifier<ICompletionsTelemetryService>('completionsTelemetryService');
export interface ICompletionsTelemetryService {
readonly _serviceBrand: undefined;
sendGHTelemetryEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void;
sendEnhancedGHTelemetryEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void;
sendGHTelemetryErrorEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void;
sendGHTelemetryException(maybeError: unknown, origin: string, store?: TelemetryStore): void;
setSpyReporters(reporter: TelemetrySpy, enhancedReporter: TelemetrySpy): void;
clearSpyReporters(): void;
}
export class CompletionsTelemetryServiceBridge implements ICompletionsTelemetryService {
declare _serviceBrand: undefined;
private reporter: TelemetrySpy | undefined;
private enhancedReporter: TelemetrySpy | undefined;
constructor(
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
this.reporter = undefined;
this.enhancedReporter = undefined;
}
sendGHTelemetryEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void {
this.telemetryService.sendGHTelemetryEvent(wrapEventNameForPrefixRemoval(`copilot/${eventName}`), properties, measurements);
this.getSpyReporters(store ?? TelemetryStore.Standard)?.sendTelemetryEvent(eventName, properties as TelemetryProperties, measurements as TelemetryMeasurements);
}
sendEnhancedGHTelemetryEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void {
this.telemetryService.sendEnhancedGHTelemetryEvent(wrapEventNameForPrefixRemoval(`copilot/${eventName}`), properties, measurements);
this.getSpyReporters(store ?? TelemetryStore.Enhanced)?.sendTelemetryEvent(eventName, properties as TelemetryProperties, measurements as TelemetryMeasurements);
}
sendGHTelemetryErrorEvent(eventName: string, properties?: TelemetryEventProperties, measurements?: TelemetryEventMeasurements, store?: TelemetryStore): void {
this.telemetryService.sendGHTelemetryErrorEvent(wrapEventNameForPrefixRemoval(`copilot/${eventName}`), properties, measurements);
this.getSpyReporters(store ?? TelemetryStore.Enhanced)?.sendTelemetryErrorEvent(eventName, properties as TelemetryProperties, measurements as TelemetryMeasurements);
}
sendGHTelemetryException(maybeError: unknown, origin: string, store?: TelemetryStore): void {
this.telemetryService.sendGHTelemetryException(maybeError, origin);
if (maybeError instanceof Error) {
this.getSpyReporters(store ?? TelemetryStore.Enhanced)?.sendTelemetryException(maybeError as Error, undefined, undefined);
}
}
setSpyReporters(reporter: TelemetrySpy, enhancedReporter: TelemetrySpy) {
this.reporter = reporter;
this.enhancedReporter = enhancedReporter;
}
clearSpyReporters() {
this.reporter = undefined;
this.enhancedReporter = undefined;
}
private getSpyReporters(store: TelemetryStore): TelemetrySpy | undefined {
if (TelemetryStore.isEnhanced(store)) {
return this.enhancedReporter;
} else {
return this.reporter;
}
}
}
@@ -1,262 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, env } from 'vscode';
import { ILogService } from '../../../platform/log/common/logService';
import { outputChannel } from '../../../platform/log/vscode/outputChannelLogTarget';
import { DisposableStore, IDisposable } from '../../../util/vs/base/common/lifecycle';
import { URI } from '../../../util/vs/base/common/uri';
import { SyncDescriptor } from '../../../util/vs/platform/instantiation/common/descriptors';
import { IInstantiationService, ServicesAccessor } from '../../../util/vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from '../../../util/vs/platform/instantiation/common/serviceCollection';
import { CompletionsTelemetryServiceBridge, ICompletionsTelemetryService } from './bridge/src/completionsTelemetryServiceBridge';
import { LoggingCitationManager } from './extension/src/codeReferencing/citationManager';
import { CompletionsObservableWorkspace } from './extension/src/completionsObservableWorkspace';
import { disableCompletions, enableCompletions, toggleCompletions, VSCodeConfigProvider, VSCodeEditorInfo } from './extension/src/config';
import { CMDDisableCompletionsChat, CMDDisableCompletionsClient, CMDEnableCompletionsChat, CMDEnableCompletionsClient, CMDOpenDocumentationClient, CMDOpenLogsClient, CMDOpenModelPickerChat, CMDOpenModelPickerClient, CMDToggleCompletionsChat, CMDToggleCompletionsClient, CMDToggleStatusMenuChat, CMDToggleStatusMenuClient } from './extension/src/constants';
import { contextProviderMatch } from './extension/src/contextProviderMatch';
import { registerPanelSupport } from './extension/src/copilotPanel/common';
import { CopilotExtensionStatus, ICompletionsExtensionStatus } from './extension/src/extensionStatus';
import { extensionFileSystem } from './extension/src/fileSystem';
import { ModelPickerManager } from './extension/src/modelPicker';
import { CopilotStatusBar } from './extension/src/statusBar';
import { CopilotStatusBarPickMenu } from './extension/src/statusBarPicker';
import { ExtensionTextDocumentManager } from './extension/src/textDocumentManager';
import { exception } from './extension/src/vscodeInlineCompletionItemProvider';
import { CopilotTokenManagerImpl, ICompletionsCopilotTokenManager } from './lib/src/auth/copilotTokenManager';
import { ICompletionsCitationManager } from './lib/src/citationManager';
import { CompletionNotifier, ICompletionsNotifierService } from './lib/src/completionNotifier';
import { ICompletionsObservableWorkspace } from './lib/src/completionsObservableWorkspace';
import { ICompletionsConfigProvider, ICompletionsEditorAndPluginInfo } from './lib/src/config';
import { registerDocumentTracker } from './lib/src/documentTracker';
import { ICompletionsUserErrorNotifierService, UserErrorNotifier } from './lib/src/error/userErrorNotifier';
import { setupCompletionsExperimentationService } from './lib/src/experiments/defaultExpFilters';
import { Features } from './lib/src/experiments/features';
import { ICompletionsFeaturesService } from './lib/src/experiments/featuresService';
import { FileReader, ICompletionsFileReaderService } from './lib/src/fileReader';
import { ICompletionsFileSystemService } from './lib/src/fileSystem';
import { AsyncCompletionManager, ICompletionsAsyncManagerService } from './lib/src/ghostText/asyncCompletions';
import { CompletionsCache, ICompletionsCacheService } from './lib/src/ghostText/completionsCache';
import { ConfigBlockModeConfig, ICompletionsBlockModeConfig } from './lib/src/ghostText/configBlockMode';
import { CurrentGhostText, ICompletionsCurrentGhostText } from './lib/src/ghostText/current';
import { ICompletionsLastGhostText, LastGhostText } from './lib/src/ghostText/last';
import { ICompletionsSpeculativeRequestCache, SpeculativeRequestCache } from './lib/src/ghostText/speculativeRequestCache';
import { ICompletionsLogTargetService, LogLevel } from './lib/src/logger';
import { formatLogMessage } from './lib/src/logging/util';
import { CompletionsFetcher, ICompletionsFetcherService } from './lib/src/networking';
import { ExtensionNotificationSender, ICompletionsNotificationSender } from './lib/src/notificationSender';
import { ICompletionsOpenAIFetcherService, LiveOpenAIFetcher } from './lib/src/openai/fetch';
import { AvailableModelsManager, ICompletionsModelManagerService } from './lib/src/openai/model';
import { ICompletionsStatusReporter } from './lib/src/progress';
import {
CompletionsPromptFactory, ICompletionsPromptFactoryService
} from './lib/src/prompt/completionsPromptFactory/completionsPromptFactory';
import { ContextProviderBridge, ICompletionsContextProviderBridgeService } from './lib/src/prompt/components/contextProviderBridge';
import {
CachedContextProviderRegistry,
CoreContextProviderRegistry,
DefaultContextProvidersContainer, ICompletionsContextProviderRegistryService,
ICompletionsDefaultContextProviders
} from './lib/src/prompt/contextProviderRegistry';
import { ContextProviderStatistics, ICompletionsContextProviderService } from './lib/src/prompt/contextProviderStatistics';
import { FullRecentEditsProvider, ICompletionsRecentEditsProviderService } from './lib/src/prompt/recentEdits/recentEditsProvider';
import { CompositeRelatedFilesProvider } from './lib/src/prompt/similarFiles/compositeRelatedFilesProvider';
import { ICompletionsRelatedFilesProviderService } from './lib/src/prompt/similarFiles/relatedFiles';
import { ICompletionsTelemetryUserConfigService, TelemetryUserConfig } from './lib/src/telemetry/userConfig';
import { ICompletionsTextDocumentManagerService } from './lib/src/textDocumentManager';
import { ICompletionsPromiseQueueService, PromiseQueue } from './lib/src/util/promiseQueue';
import { ICompletionsRuntimeModeService, RuntimeMode } from './lib/src/util/runtimeMode';
/** @public */
export function createContext(serviceAccessor: ServicesAccessor, store: DisposableStore): IInstantiationService {
const logService = serviceAccessor.get(ILogService);
const serviceCollection = new ServiceCollection();
serviceCollection.set(ICompletionsLogTargetService, new class implements ICompletionsLogTargetService {
declare _serviceBrand: undefined;
logIt(level: LogLevel, category: string, ...extra: unknown[]): void {
const msg = formatLogMessage(category, ...extra);
switch (level) {
case LogLevel.DEBUG: return logService.debug(msg);
case LogLevel.INFO: return logService.info(msg);
case LogLevel.WARN: return logService.warn(msg);
case LogLevel.ERROR: return logService.error(msg);
}
}
});
serviceCollection.set(ICompletionsRuntimeModeService, RuntimeMode.fromEnvironment(false));
serviceCollection.set(ICompletionsCacheService, new CompletionsCache());
serviceCollection.set(ICompletionsConfigProvider, new VSCodeConfigProvider());
serviceCollection.set(ICompletionsLastGhostText, new LastGhostText());
serviceCollection.set(ICompletionsCurrentGhostText, new CurrentGhostText());
serviceCollection.set(ICompletionsSpeculativeRequestCache, new SpeculativeRequestCache());
serviceCollection.set(ICompletionsNotificationSender, new SyncDescriptor(ExtensionNotificationSender));
serviceCollection.set(ICompletionsEditorAndPluginInfo, new VSCodeEditorInfo());
serviceCollection.set(ICompletionsExtensionStatus, new CopilotExtensionStatus());
serviceCollection.set(ICompletionsFeaturesService, new SyncDescriptor(Features));
serviceCollection.set(ICompletionsObservableWorkspace, new SyncDescriptor(CompletionsObservableWorkspace));
serviceCollection.set(ICompletionsStatusReporter, new SyncDescriptor(CopilotStatusBar, ['github.copilot.languageStatus']));
serviceCollection.set(ICompletionsCopilotTokenManager, new SyncDescriptor(CopilotTokenManagerImpl, [false]));
serviceCollection.set(ICompletionsTextDocumentManagerService, new SyncDescriptor(ExtensionTextDocumentManager));
serviceCollection.set(ICompletionsFileReaderService, new SyncDescriptor(FileReader));
serviceCollection.set(ICompletionsBlockModeConfig, new SyncDescriptor(ConfigBlockModeConfig));
serviceCollection.set(ICompletionsTelemetryService, new SyncDescriptor(CompletionsTelemetryServiceBridge));
serviceCollection.set(ICompletionsTelemetryUserConfigService, new SyncDescriptor(TelemetryUserConfig));
serviceCollection.set(ICompletionsRecentEditsProviderService, new SyncDescriptor(FullRecentEditsProvider, [undefined]));
serviceCollection.set(ICompletionsNotifierService, new SyncDescriptor(CompletionNotifier));
serviceCollection.set(ICompletionsOpenAIFetcherService, new SyncDescriptor(LiveOpenAIFetcher));
serviceCollection.set(ICompletionsModelManagerService, new SyncDescriptor(AvailableModelsManager, [true]));
serviceCollection.set(ICompletionsAsyncManagerService, new SyncDescriptor(AsyncCompletionManager));
serviceCollection.set(ICompletionsContextProviderBridgeService, new SyncDescriptor(ContextProviderBridge));
serviceCollection.set(ICompletionsUserErrorNotifierService, new SyncDescriptor(UserErrorNotifier));
serviceCollection.set(ICompletionsRelatedFilesProviderService, new SyncDescriptor(CompositeRelatedFilesProvider));
serviceCollection.set(ICompletionsFileSystemService, extensionFileSystem);
serviceCollection.set(ICompletionsContextProviderRegistryService, new SyncDescriptor(CachedContextProviderRegistry, [CoreContextProviderRegistry, contextProviderMatch]));
serviceCollection.set(ICompletionsPromiseQueueService, new PromiseQueue());
serviceCollection.set(ICompletionsCitationManager, new SyncDescriptor(LoggingCitationManager));
serviceCollection.set(ICompletionsContextProviderService, new ContextProviderStatistics());
serviceCollection.set(ICompletionsPromptFactoryService, new SyncDescriptor(CompletionsPromptFactory));
serviceCollection.set(ICompletionsFetcherService, new SyncDescriptor(CompletionsFetcher));
serviceCollection.set(ICompletionsDefaultContextProviders, new DefaultContextProvidersContainer());
return serviceAccessor.get(IInstantiationService).createChild(serviceCollection, store);
}
/** @public */
export function setup(serviceAccessor: ServicesAccessor, disposables: DisposableStore) {
// This must be registered before activation!
// CodeQuote needs to listen for the initial token notification event.
disposables.add(serviceAccessor.get(ICompletionsCitationManager).register());
// Register to listen for changes to the active document to keep track
// of last access time
disposables.add(registerDocumentTracker(serviceAccessor));
// Register the context providers enabled by default.
const defaultContextProviders = serviceAccessor.get(ICompletionsDefaultContextProviders);
defaultContextProviders.add('ms-vscode.cpptools');
defaultContextProviders.add('promptfile-ai-context-provider');
disposables.add(setupCompletionsExperimentationService(serviceAccessor));
}
export function registerUnificationCommands(accessor: ServicesAccessor): IDisposable {
const disposables = new DisposableStore();
disposables.add(registerEnablementCommands(accessor));
disposables.add(registerStatusBar(accessor));
disposables.add(registerDiagnosticCommands(accessor));
disposables.add(registerPanelSupport(accessor));
disposables.add(registerModelPickerCommands(accessor));
return disposables;
}
function registerEnablementCommands(accessor: ServicesAccessor): IDisposable {
const disposables = new DisposableStore();
const instantiationService = accessor.get(IInstantiationService);
// Enable/Disable/Toggle completions commands [with Command Palette support]
function enable(id: string): IDisposable {
return registerCommandWrapper(accessor, id, async () => {
await instantiationService.invokeFunction(enableCompletions);
});
}
function disable(id: string): IDisposable {
return registerCommandWrapper(accessor, id, async () => {
await instantiationService.invokeFunction(disableCompletions);
});
}
function toggle(id: string): IDisposable {
return registerCommandWrapper(accessor, id, async () => {
await instantiationService.invokeFunction(toggleCompletions);
});
}
// To support command palette
disposables.add(enable(CMDEnableCompletionsChat));
disposables.add(disable(CMDDisableCompletionsChat));
disposables.add(toggle(CMDToggleCompletionsChat));
// To support keybindings/main functionality
disposables.add(enable(CMDEnableCompletionsClient));
disposables.add(disable(CMDDisableCompletionsClient));
disposables.add(toggle(CMDToggleCompletionsClient));
return disposables;
}
function registerModelPickerCommands(accessor: ServicesAccessor): IDisposable {
const disposables = new DisposableStore();
const instantiationService = accessor.get(IInstantiationService);
const modelsPicker = instantiationService.createInstance(ModelPickerManager);
function registerModelPicker(commandId: string): IDisposable {
return registerCommandWrapper(accessor, commandId, async () => {
await modelsPicker.showModelPicker();
});
}
// Model picker command [with Command Palette support]
disposables.add(registerModelPicker(CMDOpenModelPickerClient));
disposables.add(registerModelPicker(CMDOpenModelPickerChat));
return disposables;
}
function registerStatusBar(accessor: ServicesAccessor): IDisposable {
const disposables = new DisposableStore();
const instantiationService = accessor.get(IInstantiationService);
const copilotTokenManagerService = accessor.get(ICompletionsCopilotTokenManager);
const extensionStatusService = accessor.get(ICompletionsExtensionStatus);
// Status menu command [with Command Palette support]
function registerStatusMenu(menuId: string): IDisposable {
return registerCommandWrapper(accessor, menuId, async () => {
if (extensionStatusService.kind === 'Error') {
// Try for a fresh token to clear up the error, but don't block the UI for too long.
await Promise.race([
copilotTokenManagerService.primeToken(),
new Promise(resolve => setTimeout(resolve, 100)),
]);
}
instantiationService.createInstance(CopilotStatusBarPickMenu).showStatusMenu();
});
}
disposables.add(registerStatusMenu(CMDToggleStatusMenuClient));
disposables.add(registerStatusMenu(CMDToggleStatusMenuChat));
return disposables;
}
function registerDiagnosticCommands(accessor: ServicesAccessor): IDisposable {
const disposables = new DisposableStore();
disposables.add(registerCommandWrapper(accessor, CMDOpenDocumentationClient, () => {
return env.openExternal(
URI.parse('https://docs.github.com/en/copilot/getting-started-with-github-copilot?tool=vscode')
);
}));
disposables.add(registerCommandWrapper(accessor, CMDOpenLogsClient, () => {
outputChannel.show();
}));
return disposables;
}
export function registerCommandWrapper(accessor: ServicesAccessor, command: string, fn: (...args: unknown[]) => unknown): IDisposable {
const instantiationService = accessor.get(IInstantiationService);
return commands.registerCommand(command, async (...args: unknown[]) => {
try {
await fn(...args);
} catch (error) {
instantiationService.invokeFunction(exception, error, command);
}
});
}
@@ -1,71 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands } from 'vscode';
import { CodeReference } from '.';
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
import { Disposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { onCopilotToken } from '../../../lib/src/auth/copilotTokenNotifier';
import { ICompletionsCitationManager, IPDocumentCitation } from '../../../lib/src/citationManager';
import { OutputPaneShowCommand } from '../../../lib/src/snippy/constants';
import { copilotOutputLogTelemetry } from '../../../lib/src/snippy/telemetryHandlers';
import { notify } from './matchNotifier';
import { GitHubCopilotLogger } from './outputChannel';
/**
* Citation manager that logs citations to the VS Code log. On the first citation encountered,
* the user gets a notification.
*/
export class LoggingCitationManager extends Disposable implements ICompletionsCitationManager {
declare _serviceBrand: undefined;
private logger?: GitHubCopilotLogger;
private readonly codeReference: CodeReference;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IAuthenticationService authenticationService: IAuthenticationService,
) {
super();
this.codeReference = this._register(this.instantiationService.createInstance(CodeReference));
const disposable = onCopilotToken(authenticationService, _ => {
if (this.logger) {
return;
}
this.logger = instantiationService.createInstance(GitHubCopilotLogger);
const initialNotificationCommand = commands.registerCommand(OutputPaneShowCommand, () =>
this.logger?.forceShow()
);
this.codeReference.addDisposable(initialNotificationCommand);
});
this.codeReference.addDisposable(disposable);
}
register() {
return this.codeReference.register();
}
async handleIPCodeCitation(citation: IPDocumentCitation): Promise<void> {
if (!this.codeReference.enabled || !this.logger || citation.details.length === 0) {
return;
}
const start = citation.location?.start;
const matchLocation = start ? `[Ln ${start.line + 1}, Col ${start.character + 1}]` : 'Location not available';
const shortenedMatchText = `${citation.matchingText
?.slice(0, 100)
.replace(/[\r\n\t]+|^[ \t]+/gm, ' ')
.trim()}...`;
this.logger.info(citation.inDocumentUri, `Similar code at `, matchLocation, shortenedMatchText);
for (const detail of citation.details) {
const { license, url } = detail;
this.logger.info(`License: ${license.replace('NOASSERTION', 'unknown')}, URL: ${url}`);
}
copilotOutputLogTelemetry.handleWrite({ instantiationService: this.instantiationService });
await this.instantiationService.invokeFunction(notify);
}
}
@@ -1,49 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextEditor, window } from 'vscode';
import { Disposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { copilotOutputLogTelemetry } from '../../../lib/src/snippy/telemetryHandlers';
import { citationsChannelName } from './outputChannel';
export class CodeRefEngagementTracker extends Disposable {
private activeLog = false;
constructor(@IInstantiationService private instantiationService: IInstantiationService) {
super();
this._register(window.onDidChangeActiveTextEditor((e) => this.onActiveEditorChange(e)));
this._register(window.onDidChangeVisibleTextEditors((e) => this.onVisibleEditorsChange(e)));
}
onActiveEditorChange = (editor: TextEditor | undefined) => {
if (this.isOutputLog(editor)) {
copilotOutputLogTelemetry.handleFocus({ instantiationService: this.instantiationService });
}
};
onVisibleEditorsChange = (currEditors: readonly TextEditor[]) => {
const copilotLog = currEditors.find(e => this.isOutputLog(e));
if (this.activeLog) {
if (!copilotLog) {
this.activeLog = false;
}
} else if (copilotLog) {
this.activeLog = true;
copilotOutputLogTelemetry.handleOpen({ instantiationService: this.instantiationService });
}
};
get logVisible() {
return this.activeLog;
}
private isOutputLog = (editor: TextEditor | undefined) => {
return (
editor && editor.document.uri.scheme === 'output' && editor.document.uri.path.includes(citationsChannelName)
);
};
}
@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vscode';
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
import { IDisposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { CopilotToken } from '../../../lib/src/auth/copilotTokenManager';
import { onCopilotToken } from '../../../lib/src/auth/copilotTokenNotifier';
import { ICompletionsLogTargetService } from '../../../lib/src/logger';
import { codeReferenceLogger } from '../../../lib/src/snippy/logger';
import { ICompletionsRuntimeModeService } from '../../../lib/src/util/runtimeMode';
import { CodeRefEngagementTracker } from './codeReferenceEngagementTracker';
export class CodeReference implements IDisposable {
subscriptions: Disposable | undefined;
event?: Disposable;
enabled: boolean = false;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ICompletionsRuntimeModeService readonly _runtimeMode: ICompletionsRuntimeModeService,
@ICompletionsLogTargetService private readonly _logTarget: ICompletionsLogTargetService,
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
) { }
dispose() {
this.subscriptions?.dispose();
this.event?.dispose();
}
register() {
if (!this._runtimeMode.isRunningInTest()) {
this.event = onCopilotToken(this._authenticationService, (t) => this.onCopilotToken(t));
}
return this;
}
addDisposable(disposable: Disposable) {
if (!this.subscriptions) {
this.subscriptions = Disposable.from(disposable);
} else {
this.subscriptions = Disposable.from(this.subscriptions, disposable);
}
}
onCopilotToken = (token: Omit<CopilotToken, 'token'>) => {
this.enabled = token.codeQuoteEnabled || false;
if (!token.codeQuoteEnabled) {
this.subscriptions?.dispose();
this.subscriptions = undefined;
codeReferenceLogger.debug(this._logTarget, 'Public code references are disabled.');
return;
}
codeReferenceLogger.info(this._logTarget, 'Public code references are enabled.');
this.addDisposable(this._instantiationService.createInstance(CodeRefEngagementTracker));
};
}
@@ -1,58 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, env, Uri } from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../platform/extContext/common/extensionContext';
import { IInstantiationService, ServicesAccessor } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { ICompletionsNotificationSender } from '../../../lib/src/notificationSender';
import { OutputPaneShowCommand } from '../../../lib/src/snippy/constants';
import { matchNotificationTelemetry, TelemetryActor } from '../../../lib/src/snippy/telemetryHandlers';
const matchCodeMessage =
'We found a reference to public code in a recent suggestion. To learn more about public code references, review the [documentation](https://aka.ms/github-copilot-match-public-code).';
const MatchAction = 'View reference';
const SettingAction = 'Change setting';
const CodeReferenceKey = 'codeReference.notified';
/**
* Displays a toast notification when the first code reference is found.
* The user will only ever see a single notification of this behavior.
* Displays the output panel on notification ack.
*/
export function notify(accessor: ServicesAccessor) {
const extension = accessor.get(IVSCodeExtensionContext);
const instantiationService = accessor.get(IInstantiationService);
const didNotify = extension.globalState.get<boolean>(CodeReferenceKey);
if (didNotify) {
return;
}
const notificationSender = accessor.get(ICompletionsNotificationSender);
const messageItems = [{ title: MatchAction }, { title: SettingAction }];
void notificationSender.showWarningMessage(matchCodeMessage, ...messageItems).then(async action => {
const event = { instantiationService, actor: 'user' as TelemetryActor };
switch (action?.title) {
case MatchAction: {
matchNotificationTelemetry.handleDoAction(event);
await commands.executeCommand(OutputPaneShowCommand);
break;
}
case SettingAction: {
await env.openExternal(Uri.parse('https://aka.ms/github-copilot-settings'));
break;
}
case undefined: {
matchNotificationTelemetry.handleDismiss(event);
break;
}
}
});
return extension.globalState.update(CodeReferenceKey, true);
}
@@ -1,102 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, type OutputChannel } from 'vscode';
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
import { Disposable, IDisposable, MutableDisposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { CopilotToken } from '../../../lib/src/auth/copilotTokenManager';
import { onCopilotToken } from '../../../lib/src/auth/copilotTokenNotifier';
interface GitHubLogger extends Disposable {
info(...messages: string[]): void;
forceShow(): void;
}
export const citationsChannelName = 'GitHub Copilot Log (Code References)';
// Literally taken from VS Code
function getCurrentTimestamp() {
const toTwoDigits = (v: number) => (v < 10 ? `0${v}` : v);
const toThreeDigits = (v: number) => (v < 10 ? `00${v}` : v < 100 ? `0${v}` : v);
const currentTime = new Date();
return `${currentTime.getFullYear()}-${toTwoDigits(currentTime.getMonth() + 1)}-${toTwoDigits(
currentTime.getDate()
)} ${toTwoDigits(currentTime.getHours())}:${toTwoDigits(currentTime.getMinutes())}:${toTwoDigits(
currentTime.getSeconds()
)}.${toThreeDigits(currentTime.getMilliseconds())}`;
}
class CodeReferenceOutputChannel implements IDisposable {
constructor(private output: OutputChannel) { }
info(...messages: string[]) {
this.output.appendLine(`${getCurrentTimestamp()} [info] ${messages.join(' ')}`);
}
show(preserveFocus: boolean) {
this.output.show(preserveFocus);
}
dispose() {
this.output.dispose();
}
}
export class GitHubCopilotLogger extends Disposable implements GitHubLogger {
private output = this._register(new MutableDisposable<CodeReferenceOutputChannel>());
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IAuthenticationService authenticationService: IAuthenticationService
) {
super();
this._register(onCopilotToken(authenticationService, t => this.checkCopilotToken(t)));
this.createChannel();
}
private checkCopilotToken = (token: Omit<CopilotToken, 'token'>) => {
if (token.codeQuoteEnabled) {
this.createChannel();
} else {
this.removeChannel();
}
};
private log(type: 'info', ...messages: string[]) {
const output = this.createChannel();
const [base, ...rest] = messages;
output[type](base, ...rest);
}
info(...messages: string[]) {
this.log('info', ...messages);
}
forceShow() {
// Preserve focus in the editor
this.getChannel()?.show(true);
}
private createChannel(): CodeReferenceOutputChannel {
if (this.output.value) {
return this.output.value;
}
this.output.value = new CodeReferenceOutputChannel(window.createOutputChannel(citationsChannelName, 'code-referencing'));
return this.output.value;
}
private getChannel(): CodeReferenceOutputChannel | undefined {
return this.output.value;
}
private removeChannel() {
this.output.value = undefined;
}
}
@@ -1,101 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TextEditor } from 'vscode';
import { DisposableStore } from '../../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService, ServicesAccessor } from '../../../../../../../util/vs/platform/instantiation/common/instantiation';
import { withInMemoryTelemetry } from '../../../../lib/src/test/telemetry';
import { createExtensionTestingContext } from '../../test/context';
import { CodeRefEngagementTracker } from '../codeReferenceEngagementTracker';
import { citationsChannelName } from '../outputChannel';
suite('CodeReferenceEngagementTracker', function () {
let engagementTracker: CodeRefEngagementTracker;
let accessor: ServicesAccessor;
const disposables = new DisposableStore();
setup(function () {
accessor = createExtensionTestingContext().createTestingAccessor();
engagementTracker = disposables.add(accessor.get(IInstantiationService).createInstance(CodeRefEngagementTracker));
});
teardown(function () {
disposables.clear();
});
test('sends a telemetry event when the output channel is focused', async function () {
const telemetry = await withInMemoryTelemetry(accessor, () => {
engagementTracker.onActiveEditorChange({
document: { uri: { scheme: 'output', path: citationsChannelName } },
} as TextEditor);
});
assert.ok(telemetry.reporter.events.length === 1);
assert.strictEqual(telemetry.reporter.events[0].name, 'code_referencing.github_copilot_log.focus.count');
});
test('sends a telemetry event when the output channel is focused2', async function () {
const telemetry = await withInMemoryTelemetry(accessor, () => {
engagementTracker.onActiveEditorChange({
document: { uri: { scheme: 'output', path: citationsChannelName } },
} as TextEditor);
});
assert.ok(telemetry.reporter.events.length === 1);
assert.strictEqual(telemetry.reporter.events[0].name, 'code_referencing.github_copilot_log.focus.count');
});
test('sends a telemetry event when the output channel is opened', async function () {
const telemetry = await withInMemoryTelemetry(accessor, () => {
engagementTracker.onVisibleEditorsChange([
{
document: { uri: { scheme: 'output', path: citationsChannelName } },
},
] as TextEditor[]);
});
assert.ok(telemetry.reporter.events.length === 1);
assert.strictEqual(telemetry.reporter.events[0].name, 'code_referencing.github_copilot_log.open.count');
});
test('does not send a telemetry event when the output channel is already opened', async function () {
const telemetry = await withInMemoryTelemetry(accessor, () => {
engagementTracker.onVisibleEditorsChange([
{
document: { uri: { scheme: 'output', path: citationsChannelName } },
},
] as TextEditor[]);
engagementTracker.onVisibleEditorsChange([
{
document: { uri: { scheme: 'output', path: citationsChannelName } },
},
{
document: { uri: { scheme: 'file', path: 'some-other-file.js' } },
},
] as TextEditor[]);
});
assert.ok(telemetry.reporter.events.length === 1);
});
test('tracks when the log closes internally', async function () {
const telemetry = await withInMemoryTelemetry(accessor, () => {
engagementTracker.onVisibleEditorsChange([
{
document: { uri: { scheme: 'output', path: citationsChannelName } },
},
] as TextEditor[]);
engagementTracker.onVisibleEditorsChange([
{
document: { uri: { scheme: 'file', path: 'some-other-file.js' } },
},
] as TextEditor[]);
});
assert.ok(telemetry.reporter.events.length === 1);
assert.ok(engagementTracker.logVisible === false);
});
});
@@ -1,70 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as Sinon from 'sinon';
import { Disposable, ExtensionContext } from 'vscode';
import { CodeReference } from '..';
import { CopilotToken, createTestExtendedTokenInfo } from '../../../../../../../platform/authentication/common/copilotToken';
import { generateUuid } from '../../../../../../../util/vs/base/common/uuid';
import { IInstantiationService } from '../../../../../../../util/vs/platform/instantiation/common/instantiation';
import { ConnectionState } from '../../../../lib/src/snippy/connectionState';
import { createExtensionTestingContext } from '../../test/context';
function testExtensionContext() {
return {
subscriptions: [],
};
}
suite('CodeReference', function () {
let extensionContext: ExtensionContext;
let instantiationService: IInstantiationService;
let sub: Disposable | undefined;
setup(function () {
const accessor = createExtensionTestingContext().createTestingAccessor();
instantiationService = accessor.get(IInstantiationService);
extensionContext = testExtensionContext() as unknown as ExtensionContext;
});
teardown(function () {
extensionContext.subscriptions.forEach(sub => {
sub.dispose();
});
sub?.dispose();
ConnectionState.setDisabled();
});
suite('subscriptions', function () {
test('should be undefined by default', function () {
const result = instantiationService.createInstance(CodeReference);
sub = result.subscriptions;
assert.ok(!sub);
});
test('should be updated correctly when token change events received', function () {
const codeQuote = instantiationService.createInstance(CodeReference);
const enabledToken = new CopilotToken(createTestExtendedTokenInfo({ token: `test token ${generateUuid()}`, username: 'fixedTokenManager', copilot_plan: 'unknown', code_quote_enabled: true }));
const disabledToken = new CopilotToken(createTestExtendedTokenInfo({ token: `test token ${generateUuid()}`, username: 'fixedTokenManager', copilot_plan: 'unknown', code_quote_enabled: false }));
codeQuote.onCopilotToken(enabledToken);
assert.ok(codeQuote.enabled);
assert.ok(codeQuote.subscriptions);
assert.ok(codeQuote.subscriptions instanceof Disposable);
const subSpy = Sinon.spy(codeQuote.subscriptions, 'dispose');
codeQuote.onCopilotToken(disabledToken);
assert.ok(!codeQuote.enabled);
assert.strictEqual(codeQuote.subscriptions, undefined);
assert.strictEqual(subSpy.calledOnce, true);
codeQuote.onCopilotToken(enabledToken);
assert.ok(codeQuote.enabled);
assert.notStrictEqual(codeQuote.subscriptions, undefined);
});
});
});
@@ -1,121 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import sinon from 'sinon';
import { commands, env } from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../../platform/extContext/common/extensionContext';
import { IInstantiationService, ServicesAccessor } from '../../../../../../../util/vs/platform/instantiation/common/instantiation';
import { ICompletionsNotificationSender } from '../../../../lib/src/notificationSender';
import { OutputPaneShowCommand } from '../../../../lib/src/snippy/constants';
import { withInMemoryTelemetry } from '../../../../lib/src/test/telemetry';
import { TestNotificationSender } from '../../../../lib/src/test/testHelpers';
import { createExtensionTestingContext } from '../../test/context';
import { notify } from '../matchNotifier';
suite('.match', function () {
let accessor: ServicesAccessor;
setup(function () {
accessor = createExtensionTestingContext().createTestingAccessor();
});
test('populates the globalState object', async function () {
const extensionContext = accessor.get(IVSCodeExtensionContext);
const globalState = extensionContext.globalState;
await notify(accessor);
assert.ok(globalState.get('codeReference.notified'));
});
test('notifies the user', async function () {
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performAction('View reference');
await notify(accessor);
assert.strictEqual(testNotificationSender.sentMessages.length, 1);
});
test('sends a telemetry event on view reference action', async function () {
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performAction('View reference');
const telemetry = await withInMemoryTelemetry(accessor, async accessor => {
await notify(accessor);
});
assert.strictEqual(telemetry.reporter.events.length, 1);
assert.strictEqual(telemetry.reporter.events[0].name, 'code_referencing.match_notification.acknowledge.count');
});
test('executes the output panel display command on view reference action', async function () {
const spy = sinon.spy(commands, 'executeCommand');
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performAction('View reference');
await notify(accessor);
await testNotificationSender.waitForMessages();
assert.ok(spy.calledOnce);
assert.ok(spy.calledWith(OutputPaneShowCommand));
spy.restore();
});
test('opens the settings page on change setting action', async function () {
const stub = sinon.stub(env, 'openExternal');
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performAction('Change setting');
await notify(accessor);
await testNotificationSender.waitForMessages();
assert.ok(stub.calledOnce);
assert.ok(
stub.calledWith(
sinon.match({
scheme: 'https',
authority: 'aka.ms',
path: '/github-copilot-settings',
})
)
);
stub.restore();
});
test('sends a telemetry event on notification dismissal', async function () {
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performDismiss();
const telemetry = await withInMemoryTelemetry(accessor, async accessor => {
await notify(accessor);
});
await testNotificationSender.waitForMessages();
assert.strictEqual(telemetry.reporter.events.length, 1);
assert.strictEqual(telemetry.reporter.events[0].name, 'code_referencing.match_notification.ignore.count');
});
test('does not notify if already notified', async function () {
const extensionContext = accessor.get(IVSCodeExtensionContext);
const instantiationService = accessor.get(IInstantiationService);
const globalState = extensionContext.globalState;
const testNotificationSender = accessor.get(ICompletionsNotificationSender) as TestNotificationSender;
testNotificationSender.performAction('View reference');
await globalState.update('codeReference.notified', true);
await instantiationService.invokeFunction(notify);
await testNotificationSender.waitForMessages();
assert.strictEqual(testNotificationSender.sentMessages.length, 0);
});
});
@@ -1,10 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSCodeWorkspace } from '../../../../inlineEdits/vscode-node/parts/vscodeWorkspace';
import { ICompletionsObservableWorkspace } from '../../lib/src/completionsObservableWorkspace';
export class CompletionsObservableWorkspace extends VSCodeWorkspace implements ICompletionsObservableWorkspace {
declare _serviceBrand: undefined;
}
@@ -1,249 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { WorkspaceConfiguration } from 'vscode';
import * as vscode from 'vscode';
import { IInstantiationService, ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import {
ConfigKey,
ConfigKeyType,
ConfigProvider, getConfigDefaultForKey,
getConfigKeyRecursively,
getOptionalConfigDefaultForKey,
ICompletionsConfigProvider,
ICompletionsEditorAndPluginInfo,
packageJson
} from '../../lib/src/config';
import { CopilotConfigPrefix } from '../../lib/src/constants';
import { Logger } from '../../lib/src/logger';
import { transformEvent } from '../../lib/src/util/event';
const logger = new Logger('extensionConfig');
export class VSCodeConfigProvider extends ConfigProvider {
private config: WorkspaceConfiguration;
constructor() {
super();
this.config = vscode.workspace.getConfiguration(CopilotConfigPrefix);
// Reload cached config if a workspace config change effects Copilot namespace
vscode.workspace.onDidChangeConfiguration(changeEvent => {
if (changeEvent.affectsConfiguration(CopilotConfigPrefix)) {
this.config = vscode.workspace.getConfiguration(CopilotConfigPrefix);
}
});
}
override getConfig<T>(key: ConfigKeyType): T {
return getConfigKeyRecursively<T>(this.config, key) ?? getConfigDefaultForKey(key);
}
override getOptionalConfig<T>(key: ConfigKeyType): T | undefined {
return getConfigKeyRecursively<T>(this.config, key) ?? getOptionalConfigDefaultForKey(key);
}
// Dumps config settings defined in the extension json
override dumpForTelemetry(): { [key: string]: string } {
return {};
}
override onDidChangeCopilotSettings: ConfigProvider['onDidChangeCopilotSettings'] = transformEvent(
vscode.workspace.onDidChangeConfiguration,
event => {
if (event.affectsConfiguration('github.copilot')) {
return this;
}
if (event.affectsConfiguration('github.copilot-chat')) {
return this;
}
}
);
}
// From vscode's src/vs/platform/telemetry/common/telemetryUtils.ts
const telemetryAllowedAuthorities = new Set([
'ssh-remote',
'dev-container',
'attached-container',
'wsl',
'tunnel',
'codespaces',
'amlext',
]);
export class VSCodeEditorInfo implements ICompletionsEditorAndPluginInfo {
declare _serviceBrand: undefined;
getEditorInfo() {
let devName = vscode.env.uriScheme;
if (vscode.version.endsWith('-insider')) {
devName = devName.replace(/-insiders$/, '');
}
const remoteName = vscode.env.remoteName;
if (remoteName) {
devName += `@${telemetryAllowedAuthorities.has(remoteName) ? remoteName : 'other'}`;
}
return {
name: 'vscode',
readableName: vscode.env.appName.replace(/ - Insiders$/, ''),
devName: devName,
version: vscode.version,
root: vscode.env.appRoot,
};
}
getEditorPluginInfo() {
return { name: 'copilot-chat', readableName: 'GitHub Copilot for Visual Studio Code', version: packageJson.version };
}
getRelatedPluginInfo() {
// Any additions to this list should also be added as a known filter in
// lib/src/experiments/filters.ts
return [
'ms-vscode.cpptools',
'ms-vscode.cmake-tools',
'ms-vscode.makefile-tools',
'ms-dotnettools.csdevkit',
'ms-python.python',
'ms-python.vscode-pylance',
'vscjava.vscode-java-pack',
'vscjava.vscode-java-dependency',
'vscode.typescript-language-features',
'ms-vscode.vscode-typescript-next',
'ms-dotnettools.csharp',
'github.copilot-chat',
]
.map(name => {
const extpj = vscode.extensions.getExtension(name)?.packageJSON as unknown;
if (extpj && typeof extpj === 'object' && 'version' in extpj && typeof extpj.version === 'string') {
return { name, version: extpj.version };
}
})
.filter(plugin => plugin !== undefined);
}
}
type EnabledConfigKeyType = { [key: string]: boolean };
function getEnabledConfigObject(accessor: ServicesAccessor): EnabledConfigKeyType {
const configProvider = accessor.get(ICompletionsConfigProvider);
return { '*': true, ...(configProvider.getConfig<EnabledConfigKeyType>(ConfigKey.Enable) ?? {}) };
}
function getEnabledConfig(accessor: ServicesAccessor, languageId: string): boolean {
const obj = getEnabledConfigObject(accessor);
return obj[languageId] ?? obj['*'] ?? true;
}
/**
* Checks if automatic completions are enabled for the current document by all Copilot completion settings.
* Excludes the `editor.inlineSuggest.enabled` setting.
* Return undefined if there is no current document.
*/
export function isCompletionEnabled(accessor: ServicesAccessor): boolean | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
return undefined;
}
return isCompletionEnabledForDocument(accessor, editor.document);
}
export function isCompletionEnabledForDocument(accessor: ServicesAccessor, document: vscode.TextDocument): boolean {
return getEnabledConfig(accessor, document.languageId);
}
export function isInlineSuggestEnabled(): boolean | undefined {
return vscode.workspace.getConfiguration('editor.inlineSuggest').get<boolean>('enabled');
}
type ConfigurationInspect = Exclude<ReturnType<vscode.WorkspaceConfiguration['inspect']>, undefined>;
const inspectKinds: [keyof ConfigurationInspect, vscode.ConfigurationTarget, boolean][] = [
['workspaceFolderLanguageValue', vscode.ConfigurationTarget.WorkspaceFolder, true],
['workspaceFolderValue', vscode.ConfigurationTarget.WorkspaceFolder, false],
['workspaceLanguageValue', vscode.ConfigurationTarget.Workspace, true],
['workspaceValue', vscode.ConfigurationTarget.Workspace, false],
['globalLanguageValue', vscode.ConfigurationTarget.Global, true],
['globalValue', vscode.ConfigurationTarget.Global, false],
];
function getConfigurationTargetForEnabledConfig(): vscode.ConfigurationTarget {
const inspect = vscode.workspace.getConfiguration(CopilotConfigPrefix).inspect(ConfigKey.Enable);
if (inspect?.workspaceFolderValue !== undefined) {
return vscode.ConfigurationTarget.WorkspaceFolder;
} else if (inspect?.workspaceValue !== undefined) {
return vscode.ConfigurationTarget.Workspace;
} else {
return vscode.ConfigurationTarget.Global;
}
}
/**
* Enable completions by every means possible.
*/
export async function enableCompletions(accessor: ServicesAccessor) {
const instantiationService = accessor.get(IInstantiationService);
const scope = vscode.window.activeTextEditor?.document;
// Make sure both of these settings are enabled, because that's a precondition for the user seeing inline completions.
for (const [section, option] of [['', 'editor.inlineSuggest.enabled']]) {
const config = vscode.workspace.getConfiguration(section, scope);
const inspect = config.inspect(option);
// Start from the most specific setting and work our way up to the global default.
for (const [key, target, overrideInLanguage] of inspectKinds) {
// Exit condition: if VS Code thinks the setting is enabled, we're done.
// This might be true from the start, or a call to .update() might flip it.
if (vscode.workspace.getConfiguration(section, scope).get(option)) {
break;
}
if (inspect?.[key] === false) {
await config.update(option, true, target, overrideInLanguage);
}
}
}
// The rest of this function is the inverse of disableCompletions(), updating the github.copilot.enable setting.
const languageId = vscode.window.activeTextEditor?.document.languageId;
if (!languageId) { return; }
const config = vscode.workspace.getConfiguration(CopilotConfigPrefix);
const enabledConfig = { ...instantiationService.invokeFunction(getEnabledConfigObject) };
if (!(languageId in enabledConfig)) {
enabledConfig['*'] = true;
} else {
enabledConfig[languageId] = true;
}
await config.update(ConfigKey.Enable, enabledConfig, getConfigurationTargetForEnabledConfig());
if (!instantiationService.invokeFunction(isCompletionEnabled)) {
const inspect = vscode.workspace.getConfiguration(CopilotConfigPrefix).inspect(ConfigKey.Enable);
const error = new Error(`Failed to enable completions for ${languageId}: ${JSON.stringify(inspect)}`);
instantiationService.invokeFunction(acc => logger.exception(acc, error, '.enable'));
}
}
/**
* Disable completions using the github.copilot.enable setting.
*/
export async function disableCompletions(accessor: ServicesAccessor) {
const instantiationService = accessor.get(IInstantiationService);
const languageId = vscode.window.activeTextEditor?.document.languageId;
if (!languageId) { return; }
const config = vscode.workspace.getConfiguration(CopilotConfigPrefix);
const enabledConfig = { ...instantiationService.invokeFunction(getEnabledConfigObject) };
if (!(languageId in enabledConfig)) {
enabledConfig['*'] = false;
} else if (enabledConfig[languageId]) {
enabledConfig[languageId] = false;
}
await config.update(ConfigKey.Enable, enabledConfig, getConfigurationTargetForEnabledConfig());
if (instantiationService.invokeFunction(isCompletionEnabled)) {
const inspect = vscode.workspace.getConfiguration(CopilotConfigPrefix).inspect(ConfigKey.Enable);
const error = new Error(`Failed to disable completions for ${languageId}: ${JSON.stringify(inspect)}`);
instantiationService.invokeFunction(acc => logger.exception(acc, error, '.disable'));
}
}
export async function toggleCompletions(accessor: ServicesAccessor) {
if (isCompletionEnabled(accessor) && isInlineSuggestEnabled()) {
await disableCompletions(accessor);
} else {
await enableCompletions(accessor);
}
}
@@ -1,44 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Commands ending with "Client" refer to the command ID used in the legacy Copilot extension.
// - These IDs should not appear in the package.json file
// - These IDs should be registered to support all functionality (except if this command needs to be supported when both extensions are loaded/active).
// Commands ending with "Chat" refer to the command ID used in the Copilot Chat extension.
// - These IDs should be used in package.json
// - These IDs should only be registered if they appear in the package.json (meaning the command palette) or if the command needs to be supported when both extensions are loaded/active.
export const CMDOpenPanelClient = 'github.copilot.generate';
export const CMDOpenPanelChat = 'github.copilot.chat.openSuggestionsPanel'; // "github.copilot.chat.generate" is already being used
export const CMDAcceptCursorPanelSolutionClient = 'github.copilot.acceptCursorPanelSolution';
export const CMDNavigatePreviousPanelSolutionClient = 'github.copilot.previousPanelSolution';
export const CMDNavigateNextPanelSolutionClient = 'github.copilot.nextPanelSolution';
export const CMDToggleStatusMenuClient = 'github.copilot.toggleStatusMenu';
export const CMDToggleStatusMenuChat = 'github.copilot.chat.toggleStatusMenu';
// Needs to be supported in both extensions when they are loaded/active. Requires a different ID.
export const CMDSendCompletionsFeedbackChat = 'github.copilot.chat.sendCompletionFeedback';
export const CMDEnableCompletionsChat = 'github.copilot.chat.completions.enable';
export const CMDDisableCompletionsChat = 'github.copilot.chat.completions.disable';
export const CMDToggleCompletionsChat = 'github.copilot.chat.completions.toggle';
export const CMDEnableCompletionsClient = 'github.copilot.completions.enable';
export const CMDDisableCompletionsClient = 'github.copilot.completions.disable';
export const CMDToggleCompletionsClient = 'github.copilot.completions.toggle';
export const CMDOpenLogsClient = 'github.copilot.openLogs';
export const CMDOpenDocumentationClient = 'github.copilot.openDocs';
// Existing chat command reused for diagnostics
export const CMDCollectDiagnosticsChat = 'github.copilot.debug.collectDiagnostics';
// Context variable that enable/disable panel-specific commands
export const CopilotPanelVisible = 'github.copilot.panelVisible';
export const ComparisonPanelVisible = 'github.copilot.comparisonPanelVisible';
export const CMDOpenModelPickerClient = 'github.copilot.openModelPicker';
export const CMDOpenModelPickerChat = 'github.copilot.chat.openModelPicker';
@@ -1,28 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { languages, workspace } from 'vscode';
import { DocumentSelector } from 'vscode-languageserver-protocol';
import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { isDocumentValid } from '../../lib/src/util/documentEvaluation';
import { DocumentContext } from '../../types/src';
export async function contextProviderMatch(
instantiationService: IInstantiationService,
documentSelector: DocumentSelector,
documentContext: DocumentContext
): Promise<number> {
const vscDoc = workspace.textDocuments.find(td => td.uri.toString() === documentContext.uri);
if (!vscDoc) {
return 0;
}
const result = await instantiationService.invokeFunction(isDocumentValid, documentContext);
if (result.status !== 'valid') {
return 0;
}
return languages.match(documentSelector, vscDoc);
}
@@ -1,83 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Command, commands, InlineCompletionItem, Uri } from 'vscode';
import { Disposable } from '../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService, ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { collectCompletionDiagnostics, formatDiagnosticsAsMarkdown } from '../../lib/src/diagnostics';
import { telemetry, TelemetryData } from '../../lib/src/telemetry';
import { CMDSendCompletionsFeedbackChat } from './constants';
export const sendCompletionFeedbackCommand: Command = {
command: CMDSendCompletionsFeedbackChat,
title: 'Send Copilot Completion Feedback',
tooltip: 'Send feedback about the last shown Copilot completion item',
};
export class CopilotCompletionFeedbackTracker extends Disposable {
private lastShownCopilotCompletionItem: InlineCompletionItem | undefined;
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
super();
this._register(commands.registerCommand(sendCompletionFeedbackCommand.command, async () => {
const commandArg: unknown = this.lastShownCopilotCompletionItem?.command?.arguments?.[0];
let telemetryArg: TelemetryData | undefined;
if (commandArg && typeof commandArg === 'object' && 'telemetry' in commandArg) {
if (commandArg.telemetry instanceof TelemetryData) {
telemetryArg = commandArg.telemetry;
}
}
this.instantiationService.invokeFunction(telemetry, 'ghostText.sentFeedback', telemetryArg);
await this.instantiationService.invokeFunction(openGitHubIssue, this.lastShownCopilotCompletionItem, telemetryArg);
}));
}
trackItem(item: InlineCompletionItem) {
this.lastShownCopilotCompletionItem = item;
}
}
async function openGitHubIssue(
accessor: ServicesAccessor,
item: InlineCompletionItem | undefined,
telemetry: TelemetryData | undefined
) {
const body = generateGitHubIssueBody(accessor, item, telemetry);
await commands.executeCommand('workbench.action.openIssueReporter', {
extensionId: 'github.copilot',
uri: Uri.parse('https://github.com/microsoft/vscode'),
data: body,
});
}
function generateGitHubIssueBody(
accessor: ServicesAccessor,
item: InlineCompletionItem | undefined,
telemetry: TelemetryData | undefined
) {
const diagnostics = collectCompletionDiagnostics(accessor, telemetry);
const formattedDiagnostics = formatDiagnosticsAsMarkdown(diagnostics);
if (typeof item?.insertText !== 'string') {
return '';
}
return `## Copilot Completion Feedback
### Describe the issue, feedback, or steps to reproduce it:
### Completion text:
\`\`\`
${item.insertText}
\`\`\`
<details>
<summary>Diagnostics</summary>
${formattedDiagnostics}
</details>
`;
}
@@ -1,65 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range, commands, window, type Disposable } from 'vscode';
import { CopilotNamedAnnotationList } from '../../../../../../platform/completions-core/common/openai/copilotAnnotations';
import { DisposableStore, IDisposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService, type ServicesAccessor } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import * as constants from '../constants';
import { registerCommand } from '../telemetry';
import { wrapDoc } from '../textDocumentManager';
import { CopilotSuggestionsPanelManager } from './copilotSuggestionsPanelManager';
// Exported for testing
export enum PanelNavigationType {
Previous = 'previous',
Next = 'next',
}
/**
* This interface contains data associated to a completion displayed in the panel.
*/
export interface PanelCompletion {
insertText: string;
range: Range;
copilotAnnotations?: CopilotNamedAnnotationList;
postInsertionCallback: () => PromiseLike<void> | void;
}
export function registerPanelSupport(accessor: ServicesAccessor): Disposable {
const instantiationService = accessor.get(IInstantiationService);
const suggestionsPanelManager = instantiationService.createInstance(CopilotSuggestionsPanelManager);
const disposableStore = new DisposableStore();
function registerOpenPanelCommand(id: string): IDisposable {
return registerCommand(accessor, id, async () => {
// hide ghost text while opening the generation ui
await commands.executeCommand('editor.action.inlineSuggest.hide');
await instantiationService.invokeFunction(commandOpenPanel, suggestionsPanelManager);
});
}
// Register both commands to also support command palette
disposableStore.add(registerOpenPanelCommand(constants.CMDOpenPanelChat));
disposableStore.add(registerOpenPanelCommand(constants.CMDOpenPanelClient));
// No command palette support needed for these commands
disposableStore.add(suggestionsPanelManager.registerCommands());
return disposableStore;
}
function commandOpenPanel(accessor: ServicesAccessor, suggestionsPanelManager: CopilotSuggestionsPanelManager) {
const editor = window.activeTextEditor;
if (!editor) { return; }
const wrapped = wrapDoc(editor.document);
if (!wrapped) { return; }
const { line, character } = editor.selection.active;
suggestionsPanelManager.renderPanel(editor.document, { line, character }, wrapped);
return commands.executeCommand('setContext', constants.CopilotPanelVisible, true);
}
@@ -1,49 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { IPosition, ITextDocument } from '../../../lib/src/textDocument';
import { solutionCountTarget } from '../lib/copilotPanel/common';
import { runSolutions } from '../lib/copilotPanel/panel';
import { UnformattedSolution } from '../lib/panelShared/panelTypes';
import { BaseListDocument } from '../panelShared/baseListDocument';
import { BasePanelCompletion, ISuggestionsPanel } from '../panelShared/basePanelTypes';
import { PanelCompletion } from './common';
/**
* Class representing a Open Copilot list using a ITextDocument as a way of displaying results.
* Currently only used in the VSCode extension.
*/
export class CopilotListDocument extends BaseListDocument<PanelCompletion> {
constructor(
textDocument: ITextDocument,
position: IPosition,
panel: ISuggestionsPanel,
countTarget = solutionCountTarget,
@IInstantiationService instantiationService: IInstantiationService
) {
super(textDocument, position, panel, countTarget, instantiationService);
}
protected createPanelCompletion(
unformatted: UnformattedSolution,
baseCompletion: BasePanelCompletion
): PanelCompletion {
return {
insertText: baseCompletion.insertText,
range: baseCompletion.range,
copilotAnnotations: baseCompletion.copilotAnnotations,
postInsertionCallback: baseCompletion.postInsertionCallback,
};
}
protected shouldAddSolution(newItem: PanelCompletion): boolean {
return !this.findDuplicateSolution(newItem);
}
protected runSolutionsImpl(): Promise<void> {
return this.instantiationService.invokeFunction(runSolutions, this, this);
}
}
@@ -1,53 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, WebviewPanel } from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../platform/extContext/common/extensionContext';
import { BaseSuggestionsPanel, SolutionContent, WebviewMessage } from '../panelShared/baseSuggestionsPanel';
import { PanelCompletion } from './common';
import { CopilotSuggestionsPanelManager } from './copilotSuggestionsPanelManager';
import { copilotPanelConfig } from './panelConfig';
export interface CopilotSolutionsMessage {
command: 'solutionsUpdated';
solutions: SolutionContent[];
percentage: number;
}
export class CopilotSuggestionsPanel extends BaseSuggestionsPanel<PanelCompletion> {
constructor(
webviewPanel: WebviewPanel,
document: TextDocument,
suggestionsPanelManager: CopilotSuggestionsPanelManager,
@IVSCodeExtensionContext contextService: IVSCodeExtensionContext,
) {
super(webviewPanel, document, suggestionsPanelManager, copilotPanelConfig, contextService);
}
protected renderSolutionContent(item: PanelCompletion, baseContent: SolutionContent): SolutionContent {
// Copilot panel just returns the base content without modifications
return baseContent;
}
protected createSolutionsMessage(content: SolutionContent[], percentage: number): CopilotSolutionsMessage {
return {
command: 'solutionsUpdated',
solutions: content,
percentage,
};
}
protected override async handleCustomMessage(message: WebviewMessage): Promise<boolean> {
switch (message.command) {
case 'acceptSolution': {
const solution = this.items()[message.solutionIndex];
await this.acceptSolution(solution, true);
return Promise.resolve(true);
}
default:
return Promise.resolve(false);
}
}
}
@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, WebviewPanel } from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../platform/extContext/common/extensionContext';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { IPosition, ITextDocument } from '../../../lib/src/textDocument';
import { solutionCountTarget } from '../lib/copilotPanel/common';
import { BaseSuggestionsPanelManager, ListDocumentInterface } from '../panelShared/baseSuggestionsPanelManager';
import { PanelCompletion } from './common';
import { CopilotListDocument } from './copilotListDocument';
import { CopilotSuggestionsPanel } from './copilotSuggestionsPanel';
import { copilotPanelConfig } from './panelConfig';
export class CopilotSuggestionsPanelManager extends BaseSuggestionsPanelManager<PanelCompletion> {
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IVSCodeExtensionContext extensionContext: IVSCodeExtensionContext,
) {
super(copilotPanelConfig, instantiationService, extensionContext);
}
protected createListDocument(
wrapped: ITextDocument,
position: IPosition,
panel: CopilotSuggestionsPanel
): ListDocumentInterface {
return this._instantiationService.createInstance(CopilotListDocument, wrapped, position, panel, solutionCountTarget);
}
protected createSuggestionsPanel(
panel: WebviewPanel,
document: TextDocument,
manager: this
): CopilotSuggestionsPanel {
return this._instantiationService.createInstance(CopilotSuggestionsPanel, panel, document, manager);
}
}
@@ -1,23 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as constants from '../constants';
import { CopilotPanelVisible } from '../constants';
import { PanelConfig } from '../panelShared/basePanelTypes';
// Configuration for the GitHub Copilot Suggestions Panel
export const copilotPanelConfig: PanelConfig = {
panelTitle: 'GitHub Copilot Suggestions',
webviewId: 'GitHub Copilot Suggestions',
webviewScriptName: 'suggestionsPanelWebview.js',
contextVariable: CopilotPanelVisible,
commands: {
accept: constants.CMDAcceptCursorPanelSolutionClient,
navigatePrevious: constants.CMDNavigatePreviousPanelSolutionClient,
navigateNext: constants.CMDNavigateNextPanelSolutionClient,
},
renderingMode: 'streaming',
shuffleSolutions: false,
};
@@ -1,166 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { provideVSCodeDesignSystem, vsCodeButton } from '@vscode/webview-ui-toolkit';
import DOMPurify from 'dompurify';
const solutionsContainer = document.getElementById('solutionsContainer');
const vscode = acquireVsCodeApi();
let currentFocusIndex: number = 0;
let solutionEventHandlersInitialized = false;
provideVSCodeDesignSystem().register(vsCodeButton());
type Message = {
command: string;
solutions: {
htmlSnippet: string;
citation?: {
message: string;
url: string;
};
}[];
percentage: number;
};
window.addEventListener('DOMContentLoaded', () => {
// Notify the extension that the webview is ready
vscode.postMessage({ command: 'webviewReady' });
initializeSolutionEventHandlers();
});
window.addEventListener('message', (event) => {
const message = event.data as Message; // The JSON data our extension sent
switch (message.command) {
case 'solutionsUpdated':
handleSolutionUpdate(message);
break;
case 'navigatePreviousSolution':
navigatePreviousSolution();
break;
case 'navigateNextSolution':
navigateNextSolution();
break;
}
});
function handleSolutionUpdate(message: Message) {
updateLoadingContainer(message);
if (solutionsContainer) {
solutionsContainer.innerHTML = message.solutions
.map((solution, index) => {
const renderedCitation = solution.citation
? `<p>
<span style="vertical-align: text-bottom" aria-hidden="true">Warning</span>
${DOMPurify.sanitize(solution.citation.message)}
<a href="${DOMPurify.sanitize(solution.citation.url)}" target="_blank">Inspect source code</a>
</p>`
: '';
const sanitizedSnippet = DOMPurify.sanitize(solution.htmlSnippet);
return `<h3 class='solutionHeading' id="solution-${index + 1}-heading">Suggestion ${index + 1}</h3>
<div class='snippetContainer' aria-labelledby="solution-${index + 1}-heading" role="group" data-solution-index="${index}">${sanitizedSnippet
}</div>
${DOMPurify.sanitize(renderedCitation)}
<vscode-button role="button" class="acceptButton" id="acceptButton${index}" appearance="secondary" data-solution-index="${index}">Accept suggestion ${index + 1
}</vscode-button>`;
})
.join('');
}
}
function navigatePreviousSolution() {
const snippets = document.querySelectorAll<HTMLElement>('.snippetContainer pre');
const prevIndex = currentFocusIndex - 1;
snippets[prevIndex]?.focus();
}
function navigateNextSolution() {
const snippets = document.querySelectorAll<HTMLElement>('.snippetContainer pre');
const nextIndex = (currentFocusIndex ?? -1) + 1;
if (snippets[nextIndex]) {
snippets[nextIndex].focus();
} else if (snippets[0]) {
snippets[0].focus();
}
}
function updateLoadingContainer(message: Message) {
const progressBar = document.getElementById('progress-bar') as HTMLProgressElement;
const loadingContainer = document.getElementById('loadingContainer') as HTMLDivElement;
if (!progressBar || !loadingContainer) {
return;
}
if (message.percentage >= 100) {
loadingContainer.innerHTML = `${message.solutions.length} Suggestions`;
} else {
const loadingLabelElement = loadingContainer.querySelector('label') as HTMLLabelElement;
if (loadingLabelElement.textContent !== 'Loading suggestions:\u00A0') {
loadingLabelElement.textContent = 'Loading suggestions:\u00A0';
}
progressBar.value = message.percentage;
}
}
function initializeSolutionEventHandlers(): void {
if (solutionEventHandlersInitialized || solutionsContainer === null) {
return;
}
solutionsContainer.addEventListener('focusin', (event) => {
const target = event.target as HTMLElement | null;
const index = extractSolutionIndex(target);
if (index === undefined) {
return;
}
handleFocus(index);
});
solutionsContainer.addEventListener('click', (event) => {
const target = event.target as HTMLElement | null;
const button = target?.closest('vscode-button[data-solution-index]');
if (!(button instanceof HTMLElement)) {
return;
}
const index = extractSolutionIndex(button);
if (index === undefined) {
return;
}
handleClick(index);
});
solutionEventHandlersInitialized = true;
}
function extractSolutionIndex(element: HTMLElement | null): number | undefined {
const solutionElement = element?.closest('[data-solution-index]');
if (!(solutionElement instanceof HTMLElement)) {
return undefined;
}
const attributeValue = solutionElement.getAttribute('data-solution-index');
if (attributeValue === null) {
return undefined;
}
const index = Number.parseInt(attributeValue, 10);
return Number.isNaN(index) ? undefined : index;
}
function handleFocus(index: number) {
currentFocusIndex = index;
vscode.postMessage({
command: 'focusSolution',
solutionIndex: index,
});
}
function handleClick(index: number) {
vscode.postMessage({
command: 'acceptSolution',
solutionIndex: index,
});
}
@@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2022",
"skipLibCheck": true, // https://github.com/DataDog/datadog-ci/issues/1059
"sourceMap": true,
"rootDir": ".",
"lib": ["ES2021", "dom"],
// Reset values set in the parent tsconfig
"strict": true, /* enable all strict type-checking options */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noImplicitOverride": true, /* Force use of `override` keyword. */
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"useDefineForClassFields": false,
"resolveJsonModule": true,
"experimentalDecorators": true,
"isolatedModules": false,
},
"exclude": [],
}
@@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createServiceIdentifier } from '../../../../../util/common/services';
import { Command, StatusKind } from '../../types/src';
export const ICompletionsExtensionStatus = createServiceIdentifier<ICompletionsExtensionStatus>('ICompletionsExtensionStatus');
export interface ICompletionsExtensionStatus {
readonly _serviceBrand: undefined;
kind: StatusKind;
message?: string;
busy: boolean;
command?: Command;
}
export class CopilotExtensionStatus implements ICompletionsExtensionStatus {
declare _serviceBrand: undefined;
constructor(
public kind: StatusKind = 'Normal',
public message?: string,
public busy = false,
public command?: Command
) { }
}
@@ -1,32 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileType, Uri, workspace } from 'vscode';
import { FileIdentifier, FileStat, ICompletionsFileSystemService } from '../../lib/src/fileSystem';
class ExtensionFileSystem implements ICompletionsFileSystemService {
declare _serviceBrand: undefined;
async readFileString(uri: FileIdentifier): Promise<string> {
if (typeof uri !== 'string') {
uri = uri.uri;
}
return new TextDecoder().decode(await workspace.fs.readFile(Uri.parse(uri, true)));
}
async stat(uri: FileIdentifier): Promise<FileStat> {
if (typeof uri !== 'string') {
uri = uri.uri;
}
return await workspace.fs.stat(Uri.parse(uri, true));
}
async readDirectory(uri: FileIdentifier): Promise<[string, FileType][]> {
if (typeof uri !== 'string') {
uri = uri.uri;
}
return await workspace.fs.readDirectory(Uri.parse(uri, true));
}
}
export const extensionFileSystem = new ExtensionFileSystem();
@@ -1,127 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
CancellationToken,
InlineCompletionContext,
InlineCompletionEndOfLifeReason,
InlineCompletionEndOfLifeReasonKind,
InlineCompletionItem,
InlineCompletionList,
InlineCompletionTriggerKind,
PartialAcceptInfo,
Position,
Range,
TextDocument,
window
} from 'vscode';
import { ISurveyService } from '../../../../../../platform/survey/common/surveyService';
import { assertNever } from '../../../../../../util/vs/base/common/assert';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { createCorrelationId } from '../../../../../inlineEdits/common/correlationId';
import { CopilotCompletion } from '../../../lib/src/ghostText/copilotCompletion';
import { handleGhostTextPostInsert, handleGhostTextShown, handlePartialGhostTextPostInsert } from '../../../lib/src/ghostText/last';
import { GhostText } from '../../../lib/src/inlineCompletion';
import { telemetry } from '../../../lib/src/telemetry';
import { wrapDoc } from '../textDocumentManager';
export interface GhostTextCompletionList extends InlineCompletionList {
items: GhostTextCompletionItem[];
}
export interface GhostTextCompletionItem extends InlineCompletionItem {
copilotCompletion: CopilotCompletion;
}
export class GhostTextProvider {
private readonly ghostText: GhostText;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ISurveyService private readonly _surveyService: ISurveyService,
) {
this.ghostText = this.instantiationService.createInstance(GhostText);
}
async provideInlineCompletionItems(
vscodeDoc: TextDocument,
position: Position,
context: InlineCompletionContext,
token: CancellationToken
): Promise<GhostTextCompletionList | undefined> {
const textDocument = wrapDoc(vscodeDoc);
if (!textDocument) {
return;
}
// Opportunity ID is a unique ID generated by the client relating to a single "opportunity"
// to provide some kind of suggestion to the user. Multiple requests might be made for a single
// opportunity, for example requesting a completion as well as an edit suggestion. The single ID
// allows us to correlate the different requests.
const opportunityId = context.requestUuid;
const formattingOptions = window.visibleTextEditors.find(e => e.document.uri === vscodeDoc.uri)?.options;
const rawCompletions = await this.ghostText.getInlineCompletions(textDocument, position, token, {
isCycling: context.triggerKind === InlineCompletionTriggerKind.Invoke,
selectedCompletionInfo: context.selectedCompletionInfo,
formattingOptions,
opportunityId,
});
if (!rawCompletions) {
return;
}
const items: GhostTextCompletionItem[] = rawCompletions.map(completion => {
const { start, end } = completion.range;
const newRange = new Range(start.line, start.character, end.line, end.character);
return {
insertText: completion.insertText,
range: newRange,
copilotCompletion: completion,
correlationId: createCorrelationId('completions', {}),
} satisfies GhostTextCompletionItem;
});
return { items };
}
handleDidShowCompletionItem(item: GhostTextCompletionItem) {
this.instantiationService.invokeFunction(handleGhostTextShown, item.copilotCompletion);
}
handleDidPartiallyAcceptCompletionItem(item: GhostTextCompletionItem, info: number | PartialAcceptInfo) {
if (typeof info === 'number') {
return; // deprecated API
}
this.instantiationService.invokeFunction(handlePartialGhostTextPostInsert, item.copilotCompletion, info.acceptedLength);
}
async handleEndOfLifetime(completionItem: GhostTextCompletionItem, reason: InlineCompletionEndOfLifeReason) {
const copilotCompletion = completionItem.copilotCompletion;
switch (reason.kind) {
case InlineCompletionEndOfLifeReasonKind.Accepted: {
this.instantiationService.invokeFunction(handleGhostTextPostInsert, copilotCompletion);
this._surveyService.signalUsage('completions').catch(() => {
// Ignore errors from the survey command execution
});
return;
}
case InlineCompletionEndOfLifeReasonKind.Rejected: {
this.instantiationService.invokeFunction(telemetry, 'ghostText.dismissed', copilotCompletion.telemetry);
return;
}
case InlineCompletionEndOfLifeReasonKind.Ignored: {
// @ulugbekna: no-op ?
return;
}
default: {
assertNever(reason);
}
}
}
}
@@ -1,11 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export enum Icon {
Logo = '$(copilot)',
Warning = '$(copilot-warning)',
NotConnected = '$(copilot-not-connected)',
Blocked = '$(copilot-blocked)',
}
@@ -1,6 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const solutionCountTarget = 10;
@@ -1,137 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService, type ServicesAccessor } from '../../../../../../../util/vs/platform/instantiation/common/instantiation';
import { asyncIterableMapFilter } from '../../../../lib/src/helpers/iterableHelpers';
import { ICompletionsLogTargetService, Logger } from '../../../../lib/src/logger';
import { CopilotUiKind, ICompletionsOpenAIFetcherService } from '../../../../lib/src/openai/fetch';
import { APIChoice } from '../../../../lib/src/openai/openai';
import { ICompletionsStatusReporter } from '../../../../lib/src/progress';
import { getNodeStartUtil } from '../../../../lib/src/prompt/parseBlock';
import { trimLastLine } from '../../../../lib/src/prompt/prompt';
import { postProcessChoiceInContext } from '../../../../lib/src/suggestions/suggestions';
import { LocationFactory } from '../../../../lib/src/textDocument';
import {
generateSolutionsStream,
reportSolutions,
setupCompletionParams,
setupPromptAndTelemetry,
SolutionManager,
trimChoices,
} from '../panelShared/common';
import { ISolutionHandler, SolutionsStream, UnformattedSolution } from '../panelShared/panelTypes';
const solutionsLogger = new Logger('solutions');
/**
* Given an `ISolutionManager` with the context of a specific "Open Copilot" request,
* initiate the generation of a stream of solutions for that request.
*/
export async function launchSolutions(accessor: ServicesAccessor, solutionManager: SolutionManager): Promise<SolutionsStream> {
const instantiationService = accessor.get(IInstantiationService);
const fetcherService = accessor.get(ICompletionsOpenAIFetcherService);
const logTarget = accessor.get(ICompletionsLogTargetService);
const position = solutionManager.targetPosition;
const document = solutionManager.textDocument;
// Setup prompt and telemetry using shared function
const promptSetup = await setupPromptAndTelemetry(accessor, solutionManager, 'open copilot', solutionsLogger);
if ('status' in promptSetup) {
// This is a SolutionsStream indicating an error occurred
return promptSetup;
}
const { prompt, trailingWs, telemetryData, repoInfo, ourRequestId } = promptSetup;
// Setup completion parameters using shared function
const { extra, postOptions, finishedCb, engineInfo } = instantiationService.invokeFunction(setupCompletionParams,
document,
position,
prompt,
solutionManager,
telemetryData
);
const cancellationToken = solutionManager.cancellationToken;
const completionParams = {
prompt,
languageId: document.detectedLanguageId,
repoInfo,
ourRequestId,
engineModelId: engineInfo.modelId,
count: solutionManager.solutionCountTarget,
uiKind: CopilotUiKind.Panel,
postOptions,
headers: engineInfo.headers,
extra,
};
const res = await fetcherService.fetchAndStreamCompletions(completionParams, telemetryData.extendedBy(), finishedCb, cancellationToken);
if (res.type === 'failed' || res.type === 'canceled') {
return { status: 'FinishedWithError', error: `${res.type}: ${res.reason}` };
}
let choices: AsyncIterable<APIChoice> = res.choices;
choices = trimChoices(choices);
choices = asyncIterableMapFilter(choices, choice => instantiationService.invokeFunction(postProcessChoiceInContext, document, position, choice, false, solutionsLogger));
const solutions = asyncIterableMapFilter(choices, async (apiChoice: APIChoice) => {
let display = apiChoice.completionText;
solutionsLogger.info(logTarget, `Open Copilot completion: [${apiChoice.completionText}]`);
// For completions that can happen in any location in the middle of the code we try to find the existing code
// that should be displayed in the OpenCopilot panel so the code is nicely formatted/highlighted.
// This is not needed for implement unknown function quick fix, as it will be
// always "complete" standalone function in the location suggested by TS' extension.
const displayStartPos =
(await getNodeStartUtil(document, position, apiChoice.completionText)) ??
LocationFactory.position(position.line, 0);
const [displayBefore] = trimLastLine(document.getText(LocationFactory.range(displayStartPos, position)));
display = displayBefore + display;
let completionText = apiChoice.completionText;
if (trailingWs.length > 0 && completionText.startsWith(trailingWs)) {
completionText = completionText.substring(trailingWs.length);
}
const meanLogProb = apiChoice.meanLogProb;
const meanProb: number = meanLogProb !== undefined ? Math.exp(meanLogProb) : 0;
const solutionTelemetryData = telemetryData.extendedBy({
choiceIndex: apiChoice.choiceIndex.toString(),
});
const solution: UnformattedSolution = {
completionText,
insertText: display,
range: LocationFactory.range(displayStartPos, position),
meanProb: meanProb,
meanLogProb: meanLogProb || 0,
requestId: apiChoice.requestId,
choiceIndex: apiChoice.choiceIndex,
telemetryData: solutionTelemetryData,
copilotAnnotations: apiChoice.copilotAnnotations,
};
return solution;
});
// deliberately not awaiting so that we can return quickly
const solutionsStream = generateSolutionsStream(cancellationToken, solutions[Symbol.asyncIterator]());
return solutionsStream;
}
export async function runSolutions(
accessor: ServicesAccessor,
solutionManager: SolutionManager,
solutionHandler: ISolutionHandler
): Promise<void> {
const instantiationService = accessor.get(IInstantiationService);
const statusReporter = accessor.get(ICompletionsStatusReporter);
return statusReporter.withProgress(async () => {
const nextSolution = instantiationService.invokeFunction(launchSolutions, solutionManager);
return await reportSolutions(nextSolution, solutionHandler);
});
}
@@ -1,299 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vscode';
import { generateUuid } from '../../../../../../../util/vs/base/common/uuid';
import { IInstantiationService, type ServicesAccessor } from '../../../../../../../util/vs/platform/instantiation/common/instantiation';
import { createCompletionState } from '../../../../lib/src/completionState';
import { BlockMode } from '../../../../lib/src/config';
import { ICompletionsFeaturesService } from '../../../../lib/src/experiments/featuresService';
import { ICompletionsBlockModeConfig } from '../../../../lib/src/ghostText/configBlockMode';
import { ICompletionsLogTargetService, type Logger } from '../../../../lib/src/logger';
import { getEngineRequestInfo } from '../../../../lib/src/openai/config';
import { CompletionHeaders, CompletionRequestExtra, PostOptions } from '../../../../lib/src/openai/fetch';
import { APIChoice, FinishedCallback } from '../../../../lib/src/openai/openai';
import { contextIndentation, parsingBlockFinished } from '../../../../lib/src/prompt/parseBlock';
import { extractPrompt, Prompt } from '../../../../lib/src/prompt/prompt';
import { extractRepoInfoInBackground, MaybeRepoInfo } from '../../../../lib/src/prompt/repository';
import { telemetrizePromptLength, telemetry, TelemetryData, TelemetryWithExp } from '../../../../lib/src/telemetry';
import { IPosition, ITextDocument, LocationFactory, TextDocumentContents } from '../../../../lib/src/textDocument';
import { isSupportedLanguageId } from '../../../../prompt/src/parse';
import { Position } from '../../../../types/src';
import { ISolutionHandler, SolutionsStream, UnformattedSolution } from './panelTypes';
export const solutionCountTarget = 10;
export function panelPositionForDocument(document: TextDocumentContents, position: Position): IPosition {
let returnPosition = position;
const line = document.lineAt(position.line);
if (!line.isEmptyOrWhitespace) {
returnPosition = line.range.end;
}
return returnPosition;
}
/**
* Trim trailing whitespace.
*/
export async function* trimChoices(choices: AsyncIterable<APIChoice>): AsyncIterable<APIChoice> {
for await (const choice of choices) {
const choiceCopy = { ...choice };
choiceCopy.completionText = choiceCopy.completionText.trimEnd();
yield choiceCopy;
}
}
export class SolutionManager {
private _savedTelemetryData?: TelemetryWithExp | undefined;
readonly targetPosition = panelPositionForDocument(this.textDocument, this.startPosition);
constructor(
readonly textDocument: ITextDocument,
public startPosition: IPosition,
readonly cancellationToken: CancellationToken,
readonly solutionCountTarget: number
) { }
get savedTelemetryData(): TelemetryWithExp | undefined {
return this._savedTelemetryData;
}
set savedTelemetryData(data: TelemetryWithExp | undefined) {
this._savedTelemetryData = data;
}
}
export async function reportSolutions(
nextSolutionPromise: Promise<SolutionsStream>,
solutionHandler: ISolutionHandler
): Promise<void> {
const nextSolution = await nextSolutionPromise;
switch (nextSolution.status) {
case 'Solution':
await solutionHandler.onSolution(nextSolution.solution);
await reportSolutions(nextSolution.next, solutionHandler);
break;
case 'FinishedNormally':
await solutionHandler.onFinishedNormally();
break;
case 'FinishedWithError':
await solutionHandler.onFinishedWithError(nextSolution.error);
break;
}
}
export async function generateSolutionsStream(
cancellationToken: CancellationToken,
solutions: AsyncIterator<UnformattedSolution>
): Promise<SolutionsStream> {
if (cancellationToken.isCancellationRequested) {
return { status: 'FinishedWithError', error: 'Cancelled' };
}
const nextResult = await solutions.next();
if (nextResult.done === true) {
return { status: 'FinishedNormally' };
}
return {
status: 'Solution',
solution: nextResult.value,
next: generateSolutionsStream(cancellationToken, solutions),
};
}
export function normalizeCompletionText(text: string): string {
return text.replace(/\s+/g, '');
}
/**
* Result of prompt processing setup
*/
export interface PromptSetupResult {
prompt: Prompt;
trailingWs: string;
telemetryData: TelemetryWithExp;
repoInfo: MaybeRepoInfo;
ourRequestId: string;
}
/**
* Sets up prompt extraction, telemetry, and handles common error cases.
* Returns null if an error occurred that should terminate processing.
*/
export async function setupPromptAndTelemetry(
accessor: ServicesAccessor,
solutionManager: SolutionManager,
source: 'open copilot' | 'open comparison',
solutionsLogger: Logger,
engineName?: string,
comparisonRequestId?: string
): Promise<PromptSetupResult | SolutionsStream> {
const position = solutionManager.targetPosition;
const document = solutionManager.textDocument;
const repoInfo = extractRepoInfoInBackground(accessor, document.uri);
// Telemetry setup
const ourRequestId = generateUuid();
const tempTelemetry = TelemetryData.createAndMarkAsIssued(
{
headerRequestId: ourRequestId,
languageId: document.detectedLanguageId,
source,
},
{}
);
const featuresService = accessor.get(ICompletionsFeaturesService);
const instantiationService = accessor.get(IInstantiationService);
const logTarget = accessor.get(ICompletionsLogTargetService);
// Update telemetry with experiment values
solutionManager.savedTelemetryData = await featuresService
.fetchTokenAndUpdateExPValuesAndAssignments(
{ uri: document.uri, languageId: document.detectedLanguageId },
tempTelemetry
);
// Add in comparison panel specific info
if (engineName) {
solutionManager.savedTelemetryData = solutionManager.savedTelemetryData!.extendedBy({
engineName,
});
}
if (comparisonRequestId) {
solutionManager.savedTelemetryData = solutionManager.savedTelemetryData!.extendedBy({
comparisonRequestId,
});
}
// Extract prompt
const promptResponse = await instantiationService.invokeFunction(extractPrompt,
ourRequestId,
createCompletionState(document, position),
solutionManager.savedTelemetryData!
);
// Handle prompt extraction errors
if (promptResponse.type === 'copilotContentExclusion') {
return { status: 'FinishedNormally' };
}
if (promptResponse.type === 'contextTooShort') {
return { status: 'FinishedWithError', error: 'Context too short' };
}
if (promptResponse.type === 'promptCancelled') {
return { status: 'FinishedWithError', error: 'Prompt cancelled' };
}
if (promptResponse.type === 'promptTimeout') {
return { status: 'FinishedWithError', error: 'Prompt timeout' };
}
if (promptResponse.type === 'promptError') {
return { status: 'FinishedWithError', error: 'Prompt error' };
}
const prompt = promptResponse.prompt;
const trailingWs = promptResponse.trailingWs;
// Handle trailing whitespace adjustment
if (trailingWs.length > 0) {
solutionManager.startPosition = LocationFactory.position(
solutionManager.startPosition.line,
solutionManager.startPosition.character - trailingWs.length
);
}
// Update telemetry with prompt information
solutionManager.savedTelemetryData = solutionManager.savedTelemetryData!.extendedBy(
{},
{
...telemetrizePromptLength(prompt),
solutionCount: solutionManager.solutionCountTarget,
promptEndPos: document.offsetAt(position),
}
);
solutionsLogger.debug(logTarget, 'prompt:', prompt);
instantiationService.invokeFunction(telemetry, 'solution.requested', solutionManager.savedTelemetryData);
return {
prompt,
trailingWs,
telemetryData: solutionManager.savedTelemetryData,
repoInfo,
ourRequestId,
};
}
/**
* Result of completion parameters setup
*/
export interface CompletionSetupResult {
extra: CompletionRequestExtra;
postOptions: PostOptions;
finishedCb: FinishedCallback;
engineInfo: { modelId: string; headers: CompletionHeaders };
}
/**
* Sets up block mode, completion parameters, and finished callback.
*/
export function setupCompletionParams(
accessor: ServicesAccessor,
document: ITextDocument,
position: IPosition,
prompt: Prompt,
solutionManager: SolutionManager,
telemetryData: TelemetryWithExp
): CompletionSetupResult {
// Compute block mode
const blockMode = accessor.get(ICompletionsBlockModeConfig).forLanguage(document.detectedLanguageId, telemetryData);
const isSupportedLanguage = isSupportedLanguageId(document.detectedLanguageId);
const contextIndent = contextIndentation(document, position);
const extra: CompletionRequestExtra = {
language: document.detectedLanguageId,
next_indent: contextIndent.next ?? 0,
prompt_tokens: prompt.prefixTokens ?? 0,
suffix_tokens: prompt.suffixTokens ?? 0,
};
const postOptions: PostOptions = {};
if (blockMode === BlockMode.Parsing && !isSupportedLanguage) {
postOptions['stop'] = ['\n\n', '\r\n\r\n'];
}
const engineInfo = getEngineRequestInfo(accessor, telemetryData);
let finishedCb: FinishedCallback;
switch (blockMode) {
case BlockMode.Server:
// Client knows the block is done when the completion is.
finishedCb = () => undefined;
// If requested at the top-level, don't trim at all.
extra.force_indent = contextIndent.prev ?? -1;
extra.trim_by_indentation = true;
break;
case BlockMode.ParsingAndServer:
finishedCb = isSupportedLanguage
? parsingBlockFinished(document, solutionManager.startPosition)
: () => undefined;
// If requested at the top-level, don't trim at all.
extra.force_indent = contextIndent.prev ?? -1;
extra.trim_by_indentation = true;
break;
case BlockMode.Parsing:
default:
finishedCb = isSupportedLanguage
? parsingBlockFinished(document, solutionManager.startPosition)
: () => undefined;
break;
}
return {
extra,
postOptions,
finishedCb,
engineInfo,
};
}
@@ -1,44 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CopilotNamedAnnotationList } from '../../../../../../../platform/completions-core/common/openai/copilotAnnotations';
import { RequestId } from '../../../../../../../platform/networking/common/fetch';
import { TelemetryWithExp } from '../../../../lib/src/telemetry';
import { IRange } from '../../../../lib/src/textDocument';
export interface UnformattedSolution {
/** Raw text returned by model */
completionText: string;
/** Text that should be inserted into the document, replacing the text at .range */
insertText: string;
range: IRange;
meanProb: number;
meanLogProb: number;
requestId: RequestId;
choiceIndex: number;
telemetryData: TelemetryWithExp;
copilotAnnotations?: CopilotNamedAnnotationList;
/** Optional Model ID when fetching from multiple models */
modelId?: string;
}
export interface ISolutionHandler {
onSolution(solution: UnformattedSolution): Promise<void> | void;
onFinishedNormally(): Promise<void> | void;
onFinishedWithError(error: string): Promise<void> | void;
}
/**
* A stream of solutions, ending either with 'FinishedNormally' or 'FinishedWithError'.
* This structure allows for errors to occur part way through the stream, as well as
* at the beginning.
*
* The stream is similar to an async generator, but with more information when the stream
* ends: instead of just `done` we can have `FinishedNormally` or `FinishedWithError`.
*/
export type SolutionsStream =
| { status: 'FinishedNormally' }
| { status: 'FinishedWithError'; error: string }
| { status: 'Solution'; solution: UnformattedSolution; next: Promise<SolutionsStream> };
@@ -1,148 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { env, QuickPick, QuickPickItem, QuickPickItemKind, Uri, window, workspace } from 'vscode';
import { IInstantiationService, ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { ConfigKey, getConfig } from '../../lib/src/config';
import { CopilotConfigPrefix } from '../../lib/src/constants';
import { AsyncCompletionManager, ICompletionsAsyncManagerService } from '../../lib/src/ghostText/asyncCompletions';
import { CompletionsCache, ICompletionsCacheService } from '../../lib/src/ghostText/completionsCache';
import { ICompletionsLogTargetService, Logger } from '../../lib/src/logger';
import { AvailableModelsManager, ICompletionsModelManagerService, ModelItem } from '../../lib/src/openai/model';
import { telemetry, TelemetryData } from '../../lib/src/telemetry';
const logger = new Logger('modelPicker');
interface ModelPickerItem extends Omit<ModelItem, 'preview' | 'tokenizer'>, QuickPickItem {
// Distinguish between items in the quick pick
type: 'model' | 'separator' | 'learn-more';
}
// Separator and learn-more links are always shown in the quick pick
const defaultModelPickerItems: ModelPickerItem[] = [
// Add separator after the models
{
label: '',
kind: QuickPickItemKind.Separator,
modelId: 'separator',
type: 'separator' as const,
alwaysShow: true,
},
// Add "Learn more" item at the end
{
modelId: 'learn-more',
label: 'Learn more $(link-external)',
description: '',
alwaysShow: true,
type: 'learn-more' as const,
},
];
export class ModelPickerManager {
// URL for information about Copilot models
private readonly MODELS_INFO_URL = 'https://aka.ms/CopilotCompletionsModelPickerLearnMore';
get models(): ModelItem[] {
return this._modelManager.getGenericCompletionModels();
}
private getDefaultModelId(): string {
return this._modelManager.getDefaultModelId();
}
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ICompletionsAsyncManagerService private readonly _asyncCompletionManager: AsyncCompletionManager,
@ICompletionsModelManagerService private readonly _modelManager: AvailableModelsManager,
@ICompletionsLogTargetService private readonly _logTarget: ICompletionsLogTargetService,
@ICompletionsCacheService private readonly _completionsCache: CompletionsCache
) { }
async setUserSelectedCompletionModel(modelId: string | null) {
return workspace
.getConfiguration(CopilotConfigPrefix)
.update(ConfigKey.UserSelectedCompletionModel, modelId ?? '', true);
}
async handleModelSelection(quickpickList: QuickPick<ModelPickerItem>) {
const model = quickpickList.activeItems[0];
if (model === undefined) {
return;
}
quickpickList.hide();
// Open up the link
if (model.type === 'learn-more') {
await env.openExternal(Uri.parse(this.MODELS_INFO_URL));
this._instantiationService.invokeFunction(telemetry, 'modelPicker.learnMoreClicked');
return;
}
await this.selectModel(model);
}
async selectModel(model: ModelPickerItem) {
const currentModel = this._instantiationService.invokeFunction(getUserSelectedModelConfiguration);
if (currentModel !== model.modelId) {
this._completionsCache.clear();
this._asyncCompletionManager.clear();
}
const modelSelection = model.modelId === this.getDefaultModelId() ? null : model.modelId;
await this.setUserSelectedCompletionModel(modelSelection);
if (modelSelection === null) {
logger.info(this._logTarget, `User selected default model; setting null`);
} else {
logger.info(this._logTarget, `Selected model: ${model.modelId}`);
}
this._instantiationService.invokeFunction(
telemetry,
'modelPicker.modelSelected',
TelemetryData.createAndMarkAsIssued({
engineName: modelSelection ?? 'default',
})
);
}
private modelsForModelPicker(): [string | null, ModelPickerItem[]] {
const currentModelSelection = this._instantiationService.invokeFunction(getUserSelectedModelConfiguration);
const items: ModelPickerItem[] = this.models.map(model => {
return {
modelId: model.modelId,
label: `${model.label}${model.preview ? ' (Preview)' : ''}`,
description: `(${model.modelId})`,
alwaysShow: model.modelId === this.getDefaultModelId(),
type: 'model' as const,
};
});
return [currentModelSelection, items];
}
showModelPicker(): QuickPick<ModelPickerItem> {
const [currentModelSelection, items] = this.modelsForModelPicker();
const quickPick = window.createQuickPick<ModelPickerItem>();
quickPick.title = 'Change Completions Model';
quickPick.items = [...items, ...defaultModelPickerItems];
quickPick.onDidAccept(() => this.handleModelSelection(quickPick));
const currentModelOrDefault = currentModelSelection ?? this.getDefaultModelId();
// set the currently selected model as active
const selectedItem = quickPick.items.find(item => item.modelId === currentModelOrDefault);
if (selectedItem) {
quickPick.activeItems = [selectedItem];
}
quickPick.show();
return quickPick;
}
}
function getUserSelectedModelConfiguration(accessor: ServicesAccessor): string | null {
const value = getConfig<string | null>(accessor, ConfigKey.UserSelectedCompletionModel);
return typeof value === 'string' && value.length > 0 ? value : null;
}
@@ -1,110 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Range } from 'vscode';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { postInsertionTasks } from '../../../lib/src/postInsertion';
import { countLines } from '../../../lib/src/suggestions/partialSuggestions';
import { IPosition, ITextDocument } from '../../../lib/src/textDocument';
import { normalizeCompletionText, solutionCountTarget, SolutionManager } from '../lib/panelShared/common';
import { UnformattedSolution } from '../lib/panelShared/panelTypes';
import { BasePanelCompletion, ISuggestionsPanel } from './basePanelTypes';
// BaseListDocument to be shared with both the copilot and comparison completion panels.
export abstract class BaseListDocument<TPanelCompletion extends BasePanelCompletion> extends SolutionManager {
private _solutionCount = 0;
protected readonly _solutions: TPanelCompletion[] = [];
constructor(
textDocument: ITextDocument,
position: IPosition,
readonly panel: ISuggestionsPanel,
countTarget = solutionCountTarget,
@IInstantiationService protected readonly instantiationService: IInstantiationService
) {
super(textDocument, position, panel.cancellationToken, countTarget);
}
protected abstract createPanelCompletion(
unformatted: UnformattedSolution,
baseCompletion: BasePanelCompletion
): TPanelCompletion;
protected abstract shouldAddSolution(newItem: TPanelCompletion): boolean;
protected abstract runSolutionsImpl(): Promise<void>;
// Find if two solutions are duplicates by comparing their normalized text content.
protected areSolutionsDuplicates(solutionA: TPanelCompletion, solutionB: TPanelCompletion): boolean {
const stripA = normalizeCompletionText(solutionA.insertText);
const stripB = normalizeCompletionText(solutionB.insertText);
return stripA === stripB;
}
protected findDuplicateSolution(newItem: TPanelCompletion): TPanelCompletion | undefined {
return this._solutions.find(item => this.areSolutionsDuplicates(item, newItem));
}
onSolution(unformatted: UnformattedSolution) {
const offset = this.textDocument.offsetAt(this.targetPosition);
const rank = this._solutions.length;
const postInsertionCallback = () => {
const telemetryData = this.savedTelemetryData!.extendedBy(
{
choiceIndex: unformatted.choiceIndex.toString(),
engineName: unformatted.modelId || '',
},
{
compCharLen: unformatted.insertText.length,
meanProb: unformatted.meanProb,
rank,
}
);
return this.instantiationService.invokeFunction(postInsertionTasks,
'solution',
unformatted.insertText,
offset,
this.textDocument.uri,
telemetryData,
{
compType: 'full',
acceptedLength: unformatted.insertText.length,
acceptedLines: countLines(unformatted.insertText),
},
unformatted.copilotAnnotations
);
};
const baseCompletion: BasePanelCompletion = {
insertText: unformatted.insertText,
range: new Range(
new Position(unformatted.range.start.line, unformatted.range.start.character),
new Position(unformatted.range.end.line, unformatted.range.end.character)
),
copilotAnnotations: unformatted.copilotAnnotations,
postInsertionCallback,
};
const newItem = this.createPanelCompletion(unformatted, baseCompletion);
if (this.shouldAddSolution(newItem)) {
this.panel.onItem(newItem);
this._solutions.push(newItem);
}
this._solutionCount++;
this.panel.onWorkDone({ percentage: (100 * this._solutionCount) / this.solutionCountTarget });
}
onFinishedNormally() {
return this.panel.onFinished();
}
onFinishedWithError(_: string) {
return this.onFinishedNormally();
}
runQuery() {
return this.runSolutionsImpl();
}
}
@@ -1,44 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, Range } from 'vscode';
import { CopilotNamedAnnotationList } from '../../../../../../platform/completions-core/common/openai/copilotAnnotations';
// Base interface for a completion displayed in the panel.
export interface BasePanelCompletion {
insertText: string;
range: Range;
copilotAnnotations?: CopilotNamedAnnotationList;
postInsertionCallback: () => PromiseLike<void> | void;
}
// Interface for the suggestions panel, which handles work done notifications and item selections.
export interface ISuggestionsPanel {
cancellationToken: CancellationToken;
onWorkDone(_: { percentage: number }): void;
onItem(_: BasePanelCompletion): void;
onFinished(): void;
}
// Configuration for webview panels for completions.
export interface PanelConfig {
panelTitle: string;
webviewId: string;
webviewScriptName: string;
contextVariable: string;
commands: {
accept: string;
navigatePrevious: string;
navigateNext: string;
};
renderingMode: 'streaming' | 'batch';
shuffleSolutions: boolean;
}
// Configuration for webview panels, used to pass settings to the webview.
export interface WebviewConfig {
renderingMode: 'batch' | 'streaming';
shuffleSolutions: boolean;
}
@@ -1,338 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
CancellationTokenSource,
Disposable,
Event,
EventEmitter,
TextDocument,
Uri,
WebviewPanel,
WorkspaceEdit,
commands,
workspace,
} from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../platform/extContext/common/extensionContext';
import { debounce } from '../../../../../../util/common/debounce';
import { BasePanelCompletion, ISuggestionsPanel, PanelConfig } from './basePanelTypes';
import { Highlighter } from './highlighter';
import { getNonce, pluralize } from './utils';
//import { IPCitationDetail } from '#lib/citationManager';
interface IPCitationDetail {
license: string;
url: string;
}
export interface SuggestionsPanelManagerInterface {
activeWebviewPanel: BaseSuggestionsPanel<BasePanelCompletion> | undefined;
decrementPanelCount(): void;
}
export interface SolutionContent {
htmlSnippet: string;
citation?: { message: string; url: string };
[key: string]: unknown; // Allow additional properties for panel-specific content
}
export interface BaseWebviewMessage {
command: string;
}
interface AcceptSolutionMessage extends BaseWebviewMessage {
command: 'acceptSolution';
solutionIndex: number;
}
interface FocusSolutionMessage extends BaseWebviewMessage {
command: 'focusSolution';
solutionIndex: number;
}
interface SubmitFeedbackMessage extends BaseWebviewMessage {
command: 'submitFeedback';
solutionIndex: number;
feedback: string;
}
interface RefreshMessage extends BaseWebviewMessage {
command: 'refresh';
}
interface WebviewReadyMessage extends BaseWebviewMessage {
command: 'webviewReady';
}
export type WebviewMessage =
| AcceptSolutionMessage
| FocusSolutionMessage
| SubmitFeedbackMessage
| RefreshMessage
| WebviewReadyMessage;
export abstract class BaseSuggestionsPanel<TPanelCompletion extends BasePanelCompletion> implements ISuggestionsPanel {
private _disposables: Disposable[] = [];
#items: TPanelCompletion[] = [];
#batchItems: TPanelCompletion[] = [];
#percentage = 0;
#highlighter: Thenable<Highlighter>;
private _focusedSolution: TPanelCompletion | undefined;
private _isDisposed: boolean = false;
#documentUri: Uri;
#cts = new CancellationTokenSource();
private _onDidDispose = new EventEmitter<void>();
readonly onDidDispose: Event<void> = this._onDidDispose.event;
get cancellationToken() {
return this.#cts.token;
}
constructor(
readonly webviewPanel: WebviewPanel,
document: TextDocument,
protected suggestionsPanelManager: SuggestionsPanelManagerInterface,
protected readonly config: PanelConfig,
@IVSCodeExtensionContext protected readonly contextService: IVSCodeExtensionContext,
) {
webviewPanel.onDidDispose(() => this._dispose(), null, this._disposables);
webviewPanel.webview.html = this._getWebviewContent();
this.#documentUri = document.uri;
this.#highlighter = Highlighter.create(document.languageId);
workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.colorTheme')) {
return this.render();
}
});
webviewPanel.webview.onDidReceiveMessage(async (message: WebviewMessage) => {
// First lest the subclass handle custom messages
if ((await this.handleCustomMessage(message)) === true) {
return;
}
switch (message.command) {
case 'focusSolution':
this._focusedSolution = this.#items[message.solutionIndex];
return;
case 'webviewReady':
// Send the config to the webview
void this.postMessage({
command: 'updateConfig',
config: {
renderingMode: this.config.renderingMode,
shuffleSolutions: this.config.shuffleSolutions,
},
});
return;
}
}, undefined);
webviewPanel.onDidChangeViewState(e => {
if (e.webviewPanel?.visible) {
this.suggestionsPanelManager.activeWebviewPanel = this;
}
});
}
protected async handleCustomMessage(message: BaseWebviewMessage): Promise<boolean> {
return Promise.resolve(false);
}
protected abstract renderSolutionContent(item: TPanelCompletion, baseContent: SolutionContent): SolutionContent;
private _buildExtensionUri(...path: string[]): Uri {
const extensionPath = Uri.joinPath(this.contextService.extensionUri, ...path);
return this.webviewPanel.webview.asWebviewUri(extensionPath);
}
private _getWebviewContent() {
const nonce = getNonce();
const scriptUri = this._buildExtensionUri('dist', this.config.webviewScriptName);
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta
http-equiv="Content-Security-Policy"
content="default-src 'none'; font-src ${this.webviewPanel.webview.cspSource}; style-src 'unsafe-inline' ${this.webviewPanel.webview.cspSource}; script-src 'nonce-${nonce}';"
/>
<title>${this.config.panelTitle}</title>
<style>
.solutionHeading {
margin-top: 40px;
}
pre:focus-visible {
border: 1px solid var(--vscode-focusBorder);
outline: none;
}
pre {
margin-bottom: 6px;
display: block;
padding: 9.5px;
line-height: 1.42857143;
word-break: break-all;
word-wrap: break-word;
border: 1px solid #ccc;
border-radius: 4px;
border: 1px solid var(--vscode-notebook-cellBorderColor);
white-space: pre-wrap;
font-size: var(--vscode-editor-font-size);
}
pre.shiki {
padding: 0.5em 0.7em;
margin-top: 1em;
margin-bottom: 1em;
border-radius: 4px;
}
code {
background-color: transparent;
}
</style>
</head>
<body>
<h2>${this.config.panelTitle}</h2>
<div id="loadingContainer" aria-live="assertive" aria-atomic="true">
<label for="progress-bar">Loading suggestions:</label>
<progress id="progress-bar" max="100" value="0"></progress>
</div>
<div id="solutionsContainer" aria-busy="true" aria-describedby="progress-bar"></div>
<script nonce="${nonce}" type="module" src="${scriptUri.toString()}"></script>
</body>
</html>
`;
}
onWorkDone({ percentage }: { percentage: number }) {
this.#percentage = percentage;
void this.render();
}
onItem(item: TPanelCompletion) {
// If rendering mode is 'batch', we collect items and render them later
// Otherwise, we render immediately
if (this.config.renderingMode === 'batch') {
this.#batchItems.push(item);
} else {
this.#items.push(item);
void this.render();
}
}
clearSolutions() {
// Cancel any ongoing operations
this.#cts.cancel();
// Create a new cancellation token source for the next operation
this.#cts = new CancellationTokenSource();
// Clear all solutions and reset state
this.#items = [];
this.#batchItems = [];
this._focusedSolution = undefined;
this.#percentage = 0;
void this.render();
}
onFinished() {
this.#percentage = 100;
// If we have batch items, add them to the main items list, shuffle if needed, and render
if (this.#batchItems.length > 0) {
this.#items.push(...this.#batchItems);
if (this.config.shuffleSolutions) {
this.#items = this.#items.sort(() => Math.random() - 0.5);
}
this.#batchItems = [];
}
void this.render();
}
protected async acceptSolution(solution: TPanelCompletion, closePanel: boolean = true) {
if (this._isDisposed === false && solution?.range) {
const edit = new WorkspaceEdit();
edit.replace(this.#documentUri, solution.range, solution.insertText);
await workspace.applyEdit(edit);
this.#cts.cancel();
if (closePanel) {
await commands.executeCommand('workbench.action.closeActiveEditor');
}
await solution.postInsertionCallback();
}
}
protected items(): TPanelCompletion[] {
return this.#items;
}
async acceptFocusedSolution() {
const solution = this._focusedSolution;
if (solution) {
return this.acceptSolution(solution);
}
}
protected async renderSolutions() {
const highlighter = await this.#highlighter;
const content = this.#items.map(item => {
const firstCitation = item.copilotAnnotations?.ip_code_citations?.[0];
const details = firstCitation?.details.citations as IPCitationDetail[] | undefined;
let renderedCitatation: { message: string; url: string } | undefined;
if (details && details.length > 0) {
const licensesSet = new Set(details.map(d => d.license));
if (licensesSet.has('NOASSERTION')) {
licensesSet.delete('NOASSERTION');
licensesSet.add('unknown');
}
const allLicenses = Array.from(licensesSet).sort();
const licenseString = allLicenses.length === 1 ? allLicenses[0] : `[${allLicenses.join(', ')}]`;
renderedCitatation = {
message: `Similar code with ${pluralize(allLicenses.length, 'license type')} ${licenseString} detected.`,
url: details[0].url,
};
}
const baseContent = {
htmlSnippet: highlighter.createSnippet(item.insertText.trim()),
citation: renderedCitatation,
};
return this.renderSolutionContent(item, baseContent);
});
const message = this.createSolutionsMessage(content, this.#percentage);
await this.postMessage(message);
}
// Subclasses must implement this to create their specific message format
protected abstract createSolutionsMessage(content: SolutionContent[], percentage: number): unknown;
render = debounce(10, () => this.renderSolutions());
postMessage(message: unknown) {
if (this._isDisposed === false) {
return this.webviewPanel.webview.postMessage(message);
}
}
private _dispose() {
this._isDisposed = true;
this._onDidDispose.fire();
this.suggestionsPanelManager.decrementPanelCount();
while (this._disposables.length) {
const disposable = this._disposables.pop();
if (disposable) {
disposable.dispose();
}
}
this._onDidDispose.dispose();
}
}
@@ -1,98 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Uri, ViewColumn, WebviewPanel, commands, window } from 'vscode';
import { IVSCodeExtensionContext } from '../../../../../../platform/extContext/common/extensionContext';
import { DisposableStore, IDisposable } from '../../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { IPosition, ITextDocument } from '../../../lib/src/textDocument';
import { basename } from '../../../lib/src/util/uri';
import { registerCommandWrapper } from '../telemetry';
import { BasePanelCompletion, PanelConfig } from './basePanelTypes';
import { BaseSuggestionsPanel, SuggestionsPanelManagerInterface } from './baseSuggestionsPanel';
export interface ListDocumentInterface {
runQuery(): Promise<void>;
}
export abstract class BaseSuggestionsPanelManager<TPanelCompletion extends BasePanelCompletion>
implements SuggestionsPanelManagerInterface {
activeWebviewPanel: BaseSuggestionsPanel<TPanelCompletion> | undefined;
private _panelCount: number = 0;
constructor(
protected readonly config: PanelConfig,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IVSCodeExtensionContext protected readonly _extensionContext: IVSCodeExtensionContext,
) { }
protected abstract createListDocument(
wrapped: ITextDocument,
position: IPosition,
panel: BaseSuggestionsPanel<TPanelCompletion>
): ListDocumentInterface;
protected abstract createSuggestionsPanel(
panel: WebviewPanel,
document: TextDocument,
manager: this
): BaseSuggestionsPanel<TPanelCompletion>;
renderPanel(
document: TextDocument,
position: IPosition,
wrapped: ITextDocument
): BaseSuggestionsPanel<TPanelCompletion> {
const title = `${this.config.panelTitle} for ${basename(document.uri.toString()) || document.uri.toString()}`;
const panel = window.createWebviewPanel(this.config.webviewId, title, ViewColumn.Two, {
enableScripts: true,
localResourceRoots: [Uri.joinPath(this._extensionContext.extensionUri, 'dist')],
retainContextWhenHidden: true,
});
const suggestionPanel = this.createSuggestionsPanel(panel, document, this);
// Listen for the panel disposal event to clear our reference
suggestionPanel.onDidDispose(() => {
if (this.activeWebviewPanel === suggestionPanel) {
this.activeWebviewPanel = undefined;
}
});
void this.createListDocument(wrapped, position, suggestionPanel).runQuery();
this.activeWebviewPanel = suggestionPanel;
this._panelCount = this._panelCount + 1;
return suggestionPanel;
}
registerCommands(): IDisposable {
const disposableStore = new DisposableStore();
disposableStore.add(this._instantiationService.invokeFunction(registerCommandWrapper, this.config.commands.accept, () => {
return this.activeWebviewPanel?.acceptFocusedSolution();
}));
disposableStore.add(this._instantiationService.invokeFunction(registerCommandWrapper, this.config.commands.navigatePrevious, () => {
return this.activeWebviewPanel?.postMessage({
command: 'navigatePreviousSolution',
});
}));
disposableStore.add(this._instantiationService.invokeFunction(registerCommandWrapper, this.config.commands.navigateNext, () => {
return this.activeWebviewPanel?.postMessage({
command: 'navigateNextSolution',
});
}));
return disposableStore;
}
decrementPanelCount() {
this._panelCount = this._panelCount - 1;
if (this._panelCount === 0) {
void commands.executeCommand('setContext', this.config.contextVariable, false);
}
}
}
@@ -1,117 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getSingletonHighlighterCore, HighlighterCore, ThemeRegistration, ThemeRegistrationAny } from 'shiki/core';
import * as langs from 'shiki/langs';
import { BundledLanguage } from 'shiki/langs';
import getWasmInlined from 'shiki/wasm';
import { ColorThemeKind, window, workspace } from 'vscode';
import * as languages from './languages';
import * as themes from './themes';
export class Highlighter {
private constructor(
private languageId: string | undefined,
private highlighter: HighlighterCore | undefined
) { }
static async create(languageId = window.activeTextEditor?.document.languageId): Promise<Highlighter> {
if (!languageId) {
return new Highlighter(undefined, undefined);
}
const highlighter = await getSingletonHighlighterCore({
langs: Object.values(langs.bundledLanguages),
loadWasm: getWasmInlined,
});
// Load additional language if not out of the box for shiki
if (!langs.bundledLanguages[languageId as BundledLanguage]) {
const additionalLang = vscLanguageMap[languageId as keyof typeof vscLanguageMap];
if (additionalLang) {
await highlighter.loadLanguage(additionalLang);
}
}
return new Highlighter(languageId, highlighter);
}
createSnippet(text: string): string {
if (!this.highlighter || !this.languageId || !this.languageSupported()) {
return `<pre>${text}</pre>`;
}
return this.highlighter.codeToHtml(text, { lang: this.languageId, theme: getCurrentTheme() });
}
private languageSupported() {
if (!this.languageId) { return false; }
if (this.highlighter?.getLoadedLanguages().includes(this.languageId)) {
return true;
}
return false;
}
}
function getCurrentTheme(): ThemeRegistration {
const workbenchConfig = workspace.getConfiguration('workbench');
if (workbenchConfig) {
const vsCodeTheme = workbenchConfig.get<string>('colorTheme');
if (vsCodeTheme && isSupportedTheme(vsCodeTheme)) {
return vscThemeMap[vsCodeTheme];
}
const themeType = window.activeColorTheme;
const defaultTheme = vscDefaultMap[themeType.kind]; // fall back to default themes if we don't have a match
return defaultTheme;
} else {
return vscThemeMap['Default Dark Modern'];
}
}
const vscDefaultMap: { [key in ColorThemeKind]: ThemeRegistrationAny } = {
[ColorThemeKind.Dark]: themes.darkModern,
[ColorThemeKind.Light]: themes.lightModern,
[ColorThemeKind.HighContrast]: themes.darkHC,
[ColorThemeKind.HighContrastLight]: themes.lightHC,
};
// These are vs code themes that aren't out of the box in shiki but come standard with vs code
const vscThemeMap: { [key: string]: ThemeRegistrationAny } = {
Abyss: themes.abyss,
'Dark High Contrast': themes.darkHC,
'Light High Constrast': themes.lightHC,
'Default Dark Modern': themes.darkModern,
'Kimbie Dark': themes.kimbieDark,
'Default Light Modern': themes.lightModern,
'Monokai Dimmed': themes.monokaiDim,
'Quiet Light': themes.quietLight,
Red: themes.red,
'Tomorrow Night Blue': themes.tomorrowNightBlue,
'Visual Studio Dark': themes.vsDark,
'Visual Studio Light': themes.vsLight,
'Default Dark+': themes.darkPlus,
'Default Light+': themes.lightPlus,
Monokai: themes.monokai,
'Solarized Dark': themes.solarizedDark,
'Solarized Light': themes.solarizedLight,
} as const;
function isSupportedTheme(theme: keyof typeof vscThemeMap): theme is keyof typeof vscThemeMap {
return theme in vscThemeMap;
}
// These are vs code themes that aren't out of the box in shiki but come standard with vs code
const vscLanguageMap = {
'cuda-cpp': languages.cudaCpp,
javascriptreact: languages.javascriptreact,
markdown_latex_combined: languages.markdownLatexCombined,
'markdown-math': languages.markdownMath,
restructuredtext: languages.restructuredtext,
'search-result': languages.searchResult,
typescriptreact: languages.typescriptreact,
} as const;
File diff suppressed because one or more lines are too long
@@ -1,11 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export { cudaCpp } from './cuda-cpp.tmLanguage';
export { javascriptreact } from './javaScriptReact.tmLanguage';
export { markdownLatexCombined } from './markdown-latex-combined.tmLanguage';
export { markdownMath } from './md-math.tmLanguage';
export { restructuredtext } from './rst.tmLanguage';
export { searchResult } from './searchResult.tmLanguage';
export { typescriptreact } from './typeScriptReact.tmLanguage';
File diff suppressed because one or more lines are too long
@@ -1,113 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LanguageInput } from 'shiki/core';
// This file includes some grammar rules copied from https://github.com/James-Yu/LaTeX-Workshop/blob/master/syntax/TeX.tmLanguage.json',
export const markdownMath: LanguageInput = {
name: 'markdown-math',
scopeName: 'text.html.markdown.math',
patterns: [
{
include: '#math',
},
],
repository: {
$self: {},
$base: {},
math: {
patterns: [
{
name: 'comment.line.math.tex',
match: '((?<!\\\\)%)(.+)$',
captures: {
'1': {
name: 'punctuation.definition.comment.math.tex',
},
},
},
{
name: 'line.separator.math.tex',
match: '(\\\\\\\\)$',
captures: {
'1': {
name: 'punctuation.line.separator.math.tex',
},
},
},
{
name: 'meta.function.math.tex',
begin: '((\\\\)([a-zA-Z_]+))\\s*(\\{)',
beginCaptures: {
'1': {
name: 'storage.type.function.math.tex',
},
'2': {
name: 'punctuation.definition.function.math.tex',
},
'3': {
name: 'entity.name.function.math.tex',
},
'4': {
name: 'punctuation.definition.arguments.begin.math.tex',
},
},
end: '\\}',
endCaptures: {
'0': {
name: 'punctuation.definition.arguments.end.math.tex',
},
},
patterns: [
{
include: '$self',
},
],
},
{
captures: {
'1': {
name: 'punctuation.definition.constant.math.tex',
},
},
match: '(\\\\)([a-zA-Z_]+)\\b',
name: 'constant.character.math.tex',
},
{
captures: {
'1': {
name: 'punctuation.definition.constant.math.tex',
},
},
match: '(\\\\)(?!begin\\*\\{|verb)([A-Za-z]+)',
name: 'constant.other.general.math.tex',
},
{
match: '(?<!\\\\)\\{',
name: 'punctuation.math.begin.bracket.curly',
},
{
match: '(?<!\\\\)\\}',
name: 'punctuation.math.end.bracket.curly',
},
{
match: '\\(',
name: 'punctuation.math.begin.bracket.round',
},
{
match: '\\)',
name: 'punctuation.math.end.bracket.round',
},
{
match: '(([0-9]*[\\.][0-9]+)|[0-9]+)',
name: 'constant.numeric.math.tex',
},
{
match: '[\\+\\*/_\\^-]',
name: 'punctuation.math.operator.latex',
},
],
},
},
};
@@ -1,740 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable local/no-unexternalized-strings */
import { LanguageInput } from 'shiki/core';
// This file has been converted from https://github.com/trond-snekvik/vscode-rst/blob/master/syntaxes/rst.tmLanguage.json
// If you want to provide a fix or improvement, please create a pull request against the original repository.
// Once accepted there, we are happy to receive an update request.
// version: https://github.com/trond-snekvik/vscode-rst/commit/f0fe19ffde6509be52ad9267a57e1b3df665f072
export const restructuredtext: LanguageInput = {
scopeName: 'source.rst',
name: 'restructuredtext',
patterns: [
{
include: '#body',
},
],
repository: {
$self: {},
$base: {},
body: {
patterns: [
{
include: '#title',
},
{
include: '#inline-markup',
},
{
include: '#anchor',
},
{
include: '#line-block',
},
{
include: '#replace-include',
},
{
include: '#footnote',
},
{
include: '#substitution',
},
{
include: '#blocks',
},
{
include: '#table',
},
{
include: '#simple-table',
},
{
include: '#options-list',
},
],
},
title: {
match: '^(\\*{3,}|#{3,}|\\={3,}|~{3,}|\\+{3,}|-{3,}|`{3,}|\\^{3,}|:{3,}|"{3,}|_{3,}|\'{3,})$',
name: 'markup.heading',
},
'inline-markup': {
patterns: [
{
include: '#escaped',
},
{
include: '#ignore',
},
{
include: '#ref',
},
{
include: '#literal',
},
{
include: '#monospaced',
},
{
include: '#citation',
},
{
include: '#bold',
},
{
include: '#italic',
},
{
include: '#list',
},
{
include: '#macro',
},
{
include: '#reference',
},
{
include: '#footnote-ref',
},
],
},
ignore: {
patterns: [
{
match: "'[`*]+'",
},
{
match: '<[`*]+>',
},
{
match: '{[`*]+}',
},
{
match: '\\([`*]+\\)',
},
{
match: '\\[[`*]+\\]',
},
{
match: '"[`*]+"',
},
],
},
table: {
begin: '^\\s*\\+[=+-]+\\+\\s*$',
end: '^(?![+|])',
beginCaptures: {
'0': {
name: 'keyword.control.table',
},
},
patterns: [
{
match: '[=+|-]',
name: 'keyword.control.table',
},
],
},
'simple-table': {
match: '^[=\\s]+$',
name: 'keyword.control.table',
},
ref: {
begin: '(:ref:)`',
end: '`|^\\s*$',
name: 'entity.name.tag',
beginCaptures: {
'1': {
name: 'keyword.control',
},
},
patterns: [
{
match: '<.*?>',
name: 'markup.underline.link',
},
],
},
reference: {
match: '[\\w-]*[a-zA-Z\\d-]__?\\b',
name: 'entity.name.tag',
},
macro: {
match: '\\|[^\\|]+\\|',
name: 'entity.name.tag',
},
literal: {
match: '(:\\S+:)(`.*?`\\\\?)',
captures: {
'1': {
name: 'keyword.control',
},
'2': {
name: 'entity.name.tag',
},
},
},
monospaced: {
begin: '(?<=[\\s"\'(\\[{<]|^)``[^\\s`]',
end: '``|^\\s*$',
name: 'string.interpolated',
},
citation: {
begin: '(?<=[\\s"\'(\\[{<]|^)`[^\\s`]',
end: '`_{,2}|^\\s*$',
name: 'entity.name.tag',
applyEndPatternLast: false,
},
bold: {
begin: '(?<=[\\s"\'(\\[{<]|^)\\*{2}[^\\s*]',
end: '\\*{2}|^\\s*$',
name: 'markup.bold',
},
italic: {
begin: '(?<=[\\s"\'(\\[{<]|^)\\*[^\\s*]',
end: '\\*|^\\s*$',
name: 'markup.italic',
},
escaped: {
match: '\\\\.',
name: 'constant.character.escape',
},
list: {
match: '^\\s*(\\d+\\.|\\* -|[a-zA-Z#]\\.|[iIvVxXmMcC]+\\.|\\(\\d+\\)|\\d+\\)|[*+-])\\s+',
name: 'keyword.control',
},
'line-block': {
match: '^\\|\\s+',
name: 'keyword.control',
},
'raw-html': {
begin: '^(\\s*)(\\.{2}\\s+raw\\s*::)\\s+(html)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'3': {
name: 'variable.parameter.html',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'text.html.derivative',
},
],
},
anchor: {
match: '^\\.{2}\\s+(_[^:]+:)\\s*',
name: 'entity.name.tag.anchor',
},
'replace-include': {
match: '^\\s*(\\.{2})\\s+(\\|[^\\|]+\\|)\\s+(replace::)',
captures: {
'1': {
name: 'keyword.control',
},
'2': {
name: 'entity.name.tag',
},
'3': {
name: 'keyword.control',
},
},
},
footnote: {
match: '^\\s*\\.{2}\\s+\\[(?:[\\w\\.-]+|[#*]|#\\w+)\\]\\s+',
name: 'entity.name.tag',
},
'footnote-ref': {
match: '\\[(?:[\\w\\.-]+|[#*])\\]_',
name: 'entity.name.tag',
},
substitution: {
match: '^\\.{2}\\s*\\|([^|]+)\\|',
name: 'entity.name.tag',
},
'options-list': {
match: '^((?:-\\w|--[\\w-]+|/\\w+)(?:,? ?[\\w-]+)*)(?: |\\t|$)',
name: 'variable.parameter',
},
blocks: {
patterns: [
{
include: '#domains',
},
{
include: '#doctest',
},
{
include: '#code-block-cpp',
},
{
include: '#code-block-py',
},
{
include: '#code-block-console',
},
{
include: '#code-block-javascript',
},
{
include: '#code-block-yaml',
},
{
include: '#code-block-cmake',
},
{
include: '#code-block-kconfig',
},
{
include: '#code-block-ruby',
},
{
include: '#code-block-dts',
},
{
include: '#code-block',
},
{
include: '#doctest-block',
},
{
include: '#raw-html',
},
{
include: '#block',
},
{
include: '#literal-block',
},
{
include: '#block-comment',
},
],
},
'block-comment': {
begin: '^(\\s*)\\.{2}',
while: '^\\1(?=\\s)|^\\s*$',
name: 'comment.block',
},
'literal-block': {
begin: '^(\\s*)(.*)(::)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
patterns: [
{
include: '#inline-markup',
},
],
},
'3': {
name: 'keyword.control',
},
},
},
block: {
begin: '^(\\s*)(\\.{2}\\s+\\S+::)(.*)',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'3': {
name: 'variable',
},
},
patterns: [
{
include: '#block-param',
},
{
include: '#body',
},
],
},
'block-param': {
patterns: [
{
match: '(:param\\s+(.+?):)(?:\\s|$)',
captures: {
'1': {
name: 'keyword.control',
},
'2': {
name: 'variable.parameter',
},
},
},
{
match: '(:.+?:)(?:$|\\s+(.*))',
captures: {
'1': {
name: 'keyword.control',
},
'2': {
patterns: [
{
match: '\\b(0x[a-fA-F\\d]+|\\d+)\\b',
name: 'constant.numeric',
},
{
include: '#inline-markup',
},
],
},
},
},
],
},
domains: {
patterns: [
{
include: '#domain-cpp',
},
{
include: '#domain-py',
},
{
include: '#domain-auto',
},
{
include: '#domain-js',
},
],
},
'domain-cpp': {
begin: '^(\\s*)(\\.{2}\\s+(?:cpp|c):(?:class|struct|function|member|var|type|enum|enum-struct|enum-class|enumerator|union|concept)::)\\s*(?:(@\\w+)|(.*))',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'3': {
name: 'entity.name.tag',
},
'4': {
patterns: [
{
include: 'source.cpp',
},
],
},
},
patterns: [
{
include: '#block-param',
},
{
include: '#body',
},
],
},
'domain-py': {
begin: '^(\\s*)(\\.{2}\\s+py:(?:module|function|data|exception|class|attribute|property|method|staticmethod|classmethod|decorator|decoratormethod)::)\\s*(.*)',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'3': {
patterns: [
{
include: 'source.python',
},
],
},
},
patterns: [
{
include: '#block-param',
},
{
include: '#body',
},
],
},
'domain-auto': {
begin: '^(\\s*)(\\.{2}\\s+auto(?:class|module|exception|function|decorator|data|method|attribute|property)::)\\s*(.*)',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control.py',
},
'3': {
patterns: [
{
include: 'source.python',
},
],
},
},
patterns: [
{
include: '#block-param',
},
{
include: '#body',
},
],
},
'domain-js': {
begin: '^(\\s*)(\\.{2}\\s+js:\\w+::)\\s*(.*)',
end: '^(?!\\1[ \\t]|$)',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'3': {
patterns: [
{
include: 'source.js',
},
],
},
},
patterns: [
{
include: '#block-param',
},
{
include: '#body',
},
],
},
doctest: {
begin: '^(>>>)\\s*(.*)',
end: '^\\s*$',
beginCaptures: {
'1': {
name: 'keyword.control',
},
'2': {
patterns: [
{
include: 'source.python',
},
],
},
},
},
'code-block-cpp': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(c|c\\+\\+|cpp|C|C\\+\\+|CPP|Cpp)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.cpp',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.cpp',
},
],
},
'code-block-console': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(console|shell|bash)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.console',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.shell',
},
],
},
'code-block-py': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(python)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.py',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.python',
},
],
},
'code-block-javascript': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(javascript)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.js',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.js',
},
],
},
'code-block-yaml': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(ya?ml)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.yaml',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.yaml',
},
],
},
'code-block-cmake': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(cmake)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.cmake',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.cmake',
},
],
},
'code-block-kconfig': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*([kK]config)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.kconfig',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.kconfig',
},
],
},
'code-block-ruby': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(ruby)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.ruby',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.ruby',
},
],
},
'code-block-dts': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)\\s*(dts|DTS|devicetree)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
'4': {
name: 'variable.parameter.codeblock.dts',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.dts',
},
],
},
'code-block': {
begin: '^(\\s*)(\\.{2}\\s+(code|code-block)::)',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
},
patterns: [
{
include: '#block-param',
},
],
},
'doctest-block': {
begin: '^(\\s*)(\\.{2}\\s+doctest::)\\s*$',
while: '^\\1(?=\\s)|^\\s*$',
beginCaptures: {
'2': {
name: 'keyword.control',
},
},
patterns: [
{
include: '#block-param',
},
{
include: 'source.python',
},
],
},
},
};
File diff suppressed because one or more lines are too long
@@ -1,343 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const abyss: ThemeRegistrationAny = {
type: 'dark',
colors: {
'activityBar.background': '#051336',
'badge.background': '#0063a5',
'button.background': '#2b3c5d',
'debugExceptionWidget.background': '#051336',
'debugExceptionWidget.border': '#ab395b',
'debugToolBar.background': '#051336',
'diffEditor.insertedTextBackground': '#31958a55',
'diffEditor.removedTextBackground': '#892f4688',
'dropdown.background': '#181f2f',
'editor.background': '#000c18',
'editor.findMatchHighlightBackground': '#eeeeee44',
'editor.foreground': '#6688cc',
'editor.lineHighlightBackground': '#082050',
'editor.selectionBackground': '#770811',
'editorCursor.foreground': '#ddbb88',
'editorGroup.border': '#2b2b4a',
'editorGroup.dropBackground': '#25375daa',
'editorGroupHeader.tabsBackground': '#1c1c2a',
'editorHoverWidget.background': '#000c38',
'editorHoverWidget.border': '#004c18',
'editorIndentGuide.activeBackground': '#204972',
'editorIndentGuide.background': '#002952',
'editorLineNumber.activeForeground': '#80a2c2',
'editorLineNumber.foreground': '#406385',
'editorLink.activeForeground': '#0063a5',
'editorMarkerNavigation.background': '#060621',
'editorMarkerNavigationError.background': '#ab395b',
'editorMarkerNavigationWarning.background': '#5b7e7a',
'editorWhitespace.foreground': '#103050',
'editorWidget.background': '#262641',
'extensionButton.prominentBackground': '#5f8b3b',
'extensionButton.prominentHoverBackground': '#5f8b3bbb',
focusBorder: '#596f99',
'input.background': '#181f2f',
'inputOption.activeBorder': '#1d4a87',
'inputValidation.errorBackground': '#a22d44',
'inputValidation.errorBorder': '#ab395b',
'inputValidation.infoBackground': '#051336',
'inputValidation.infoBorder': '#384078',
'inputValidation.warningBackground': '#5b7e7a',
'inputValidation.warningBorder': '#5b7e7a',
'list.activeSelectionBackground': '#08286b',
'list.dropBackground': '#041d52',
'list.highlightForeground': '#0063a5',
'list.hoverBackground': '#061940',
'list.inactiveSelectionBackground': '#152037',
'minimap.selectionHighlight': '#750000',
'panel.border': '#2b2b4a',
'peekView.border': '#2b2b4a',
'peekViewEditor.background': '#10192c',
'peekViewEditor.matchHighlightBackground': '#eeeeee33',
'peekViewResult.background': '#060621',
'peekViewResult.matchHighlightBackground': '#eeeeee44',
'peekViewTitle.background': '#10192c',
'pickerGroup.border': '#596f99',
'pickerGroup.foreground': '#596f99',
'ports.iconRunningProcessForeground': '#80a2c2',
'progressBar.background': '#0063a5',
'quickInputList.focusBackground': '#08286b',
'scrollbar.shadow': '#515e91aa',
'scrollbarSlider.activeBackground': '#3b3f5188',
'scrollbarSlider.background': '#1f2230aa',
'scrollbarSlider.hoverBackground': '#3b3f5188',
'sideBar.background': '#060621',
'sideBarSectionHeader.background': '#10192c',
'statusBar.background': '#10192c',
'statusBar.debuggingBackground': '#10192c',
'statusBar.noFolderBackground': '#10192c',
'statusBarItem.prominentBackground': '#0063a5',
'statusBarItem.prominentHoverBackground': '#0063a5dd',
'statusBarItem.remoteBackground': '#0063a5',
'tab.border': '#2b2b4a',
'tab.inactiveBackground': '#10192c',
'tab.lastPinnedBorder': '#2b3c5d',
'terminal.ansiBlack': '#111111',
'terminal.ansiBlue': '#bbdaff',
'terminal.ansiBrightBlack': '#333333',
'terminal.ansiBrightBlue': '#80baff',
'terminal.ansiBrightCyan': '#78ffff',
'terminal.ansiBrightGreen': '#b8f171',
'terminal.ansiBrightMagenta': '#d778ff',
'terminal.ansiBrightRed': '#ff7882',
'terminal.ansiBrightWhite': '#ffffff',
'terminal.ansiBrightYellow': '#ffe580',
'terminal.ansiCyan': '#99ffff',
'terminal.ansiGreen': '#d1f1a9',
'terminal.ansiMagenta': '#ebbbff',
'terminal.ansiRed': '#ff9da4',
'terminal.ansiWhite': '#cccccc',
'terminal.ansiYellow': '#ffeead',
'titleBar.activeBackground': '#10192c',
},
tokenColors: [
{
scope: ['meta.embedded', 'source.groovy.embedded', 'string meta.image.inline.markdown'],
settings: {
foreground: '#6688CC',
},
},
{
scope: 'comment',
settings: {
foreground: '#384887',
},
},
{
scope: 'string',
settings: {
foreground: '#22AA44',
},
},
{
scope: 'constant.numeric',
settings: {
foreground: '#F280D0',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#F280D0',
},
},
{
scope: ['constant.character', 'constant.other'],
settings: {
foreground: '#F280D0',
},
},
{
scope: 'variable',
settings: {
fontStyle: '',
},
},
{
scope: 'keyword',
settings: {
foreground: '#225588',
},
},
{
scope: 'storage',
settings: {
foreground: '#225588',
fontStyle: '',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#9966B8',
fontStyle: 'italic',
},
},
{
scope: ['entity.name.class', 'entity.name.type', 'entity.name.namespace', 'entity.name.scope-resolution'],
settings: {
foreground: '#FFEEBB',
fontStyle: 'underline',
},
},
{
scope: 'entity.other.inherited-class',
settings: {
foreground: '#DDBB88',
fontStyle: 'italic underline',
},
},
{
scope: 'entity.name.function',
settings: {
foreground: '#DDBB88',
fontStyle: '',
},
},
{
scope: 'variable.parameter',
settings: {
foreground: '#2277FF',
fontStyle: 'italic',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#225588',
fontStyle: '',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#DDBB88',
fontStyle: '',
},
},
{
scope: 'support.function',
settings: {
foreground: '#9966B8',
fontStyle: '',
},
},
{
scope: 'support.constant',
settings: {
foreground: '#9966B8',
fontStyle: '',
},
},
{
scope: ['support.type', 'support.class'],
settings: {
foreground: '#9966B8',
fontStyle: 'italic',
},
},
{
scope: 'support.other.variable',
settings: {
fontStyle: '',
},
},
{
scope: 'invalid',
settings: {
foreground: '#A22D44',
fontStyle: '',
},
},
{
scope: 'invalid.deprecated',
settings: {
foreground: '#A22D44',
},
},
{
scope: ['meta.diff', 'meta.diff.header'],
settings: {
foreground: '#E0EDDD',
fontStyle: 'italic',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#DC322F',
fontStyle: '',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#CB4B16',
fontStyle: '',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#219186',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#22AA44',
},
},
{
scope: ['markup.bold', 'markup.italic'],
settings: {
foreground: '#22AA44',
},
},
{
scope: 'markup.bold',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#9966B8',
fontStyle: '',
},
},
{
scope: ['markup.heading', 'markup.heading.setext'],
settings: {
foreground: '#6688CC',
fontStyle: 'bold',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,462 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const darkHC: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'actionBar.toggledBackground': '#383a49',
'editor.background': '#000000',
'editor.foreground': '#ffffff',
'editor.selectionBackground': '#ffffff',
'editorIndentGuide.activeBackground1': '#ffffff',
'editorIndentGuide.background1': '#ffffff',
'editorWhitespace.foreground': '#7c7c7c',
'ports.iconRunningProcessForeground': '#ffffff',
'selection.background': '#008000',
'sideBarTitle.foreground': '#ffffff',
'statusBarItem.remoteBackground': '#00000000',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#000080',
},
},
{
scope: 'comment',
settings: {
foreground: '#7CA668',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#569CD6',
},
},
{
scope: [
'constant.numeric',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#B46695',
},
},
{
scope: 'constant.character',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'entity.name.tag.css',
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'invalid',
settings: {
foreground: '#F44747',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#6796E6',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#569CD6',
},
},
{
scope: ['punctuation.definition.tag'],
settings: {
foreground: '#808080',
},
},
{
scope: 'meta.preprocessor',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'storage',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage.modifier',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'string',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.tag',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.value',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#D16969',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#FFFFFF',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'keyword',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#D4D4D4',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.logical.python',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'variable.language.this',
settings: {
foreground: '#569CD6',
},
},
{
scope: [
'entity.name.function',
'support.function',
'support.constant.handlebars',
'source.powershell variable.other.member',
],
settings: {
foreground: '#DCDCAA',
},
},
{
scope: [
'support.class',
'support.type',
'entity.name.type',
'entity.name.namespace',
'entity.name.scope-resolution',
'entity.name.class',
'storage.type.cs',
'storage.type.generic.cs',
'storage.type.modifier.cs',
'storage.type.variable.cs',
'storage.type.annotation.java',
'storage.type.generic.java',
'storage.type.java',
'storage.type.object.array.java',
'storage.type.primitive.array.java',
'storage.type.primitive.java',
'storage.type.token.java',
'storage.type.groovy',
'storage.type.annotation.groovy',
'storage.type.parameters.groovy',
'storage.type.generic.groovy',
'storage.type.object.array.groovy',
'storage.type.primitive.array.groovy',
'storage.type.primitive.groovy',
],
settings: {
foreground: '#4EC9B0',
},
},
{
scope: [
'meta.type.cast.expr',
'meta.type.new.expr',
'support.constant.math',
'support.constant.dom',
'support.constant.json',
'entity.other.inherited-class',
],
settings: {
foreground: '#4EC9B0',
},
},
{
scope: [
'keyword.control',
'source.cpp keyword.operator.new',
'source.cpp keyword.operator.delete',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
],
settings: {
foreground: '#C586C0',
},
},
{
scope: ['variable', 'meta.definition.variable.name', 'support.variable'],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: ['meta.object-literal.key'],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#CE9178',
},
},
{
scope: 'meta.resultLinePrefix.contextLinePrefix.search',
settings: {
foreground: '#CBEDCB',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#008000',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#FF0000',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,692 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const darkModern: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'actionBar.toggledBackground': '#383a49',
'activityBar.activeBorder': '#0078d4',
'activityBar.background': '#181818',
'activityBar.border': '#2b2b2b',
'activityBar.foreground': '#d7d7d7',
'activityBar.inactiveForeground': '#868686',
'activityBarBadge.background': '#0078d4',
'activityBarBadge.foreground': '#ffffff',
'badge.background': '#616161',
'badge.foreground': '#f8f8f8',
'button.background': '#0078d4',
'button.border': '#ffffff12',
'button.foreground': '#ffffff',
'button.hoverBackground': '#026ec1',
'button.secondaryBackground': '#313131',
'button.secondaryForeground': '#cccccc',
'button.secondaryHoverBackground': '#3c3c3c',
'chat.slashCommandBackground': '#34414b',
'chat.slashCommandForeground': '#40a6ff',
'checkbox.background': '#313131',
'checkbox.border': '#3c3c3c',
'debugToolBar.background': '#181818',
descriptionForeground: '#9d9d9d',
'dropdown.background': '#313131',
'dropdown.border': '#3c3c3c',
'dropdown.foreground': '#cccccc',
'dropdown.listBackground': '#1f1f1f',
'editor.background': '#1f1f1f',
'editor.findMatchBackground': '#9e6a03',
'editor.foreground': '#cccccc',
'editor.inactiveSelectionBackground': '#3a3d41',
'editor.selectionHighlightBackground': '#add6ff26',
'editorGroup.border': '#ffffff17',
'editorGroupHeader.tabsBackground': '#181818',
'editorGroupHeader.tabsBorder': '#2b2b2b',
'editorGutter.addedBackground': '#2ea043',
'editorGutter.deletedBackground': '#f85149',
'editorGutter.modifiedBackground': '#0078d4',
'editorIndentGuide.activeBackground1': '#707070',
'editorIndentGuide.background1': '#404040',
'editorLineNumber.activeForeground': '#cccccc',
'editorLineNumber.foreground': '#6e7681',
'editorOverviewRuler.border': '#010409',
'editorWidget.background': '#202020',
errorForeground: '#f85149',
focusBorder: '#0078d4',
foreground: '#cccccc',
'icon.foreground': '#cccccc',
'input.background': '#313131',
'input.border': '#3c3c3c',
'input.foreground': '#cccccc',
'input.placeholderForeground': '#818181',
'inputOption.activeBackground': '#2489db82',
'inputOption.activeBorder': '#2488db',
'keybindingLabel.foreground': '#cccccc',
'list.activeSelectionIconForeground': '#ffffff',
'list.dropBackground': '#383b3d',
'menu.background': '#1f1f1f',
'menu.border': '#454545',
'menu.foreground': '#cccccc',
'menu.separatorBackground': '#454545',
'notificationCenterHeader.background': '#1f1f1f',
'notificationCenterHeader.foreground': '#cccccc',
'notifications.background': '#1f1f1f',
'notifications.border': '#2b2b2b',
'notifications.foreground': '#cccccc',
'panel.background': '#181818',
'panel.border': '#2b2b2b',
'panelInput.border': '#2b2b2b',
'panelTitle.activeBorder': '#0078d4',
'panelTitle.activeForeground': '#cccccc',
'panelTitle.inactiveForeground': '#9d9d9d',
'peekViewEditor.background': '#1f1f1f',
'peekViewEditor.matchHighlightBackground': '#bb800966',
'peekViewResult.background': '#1f1f1f',
'peekViewResult.matchHighlightBackground': '#bb800966',
'pickerGroup.border': '#3c3c3c',
'ports.iconRunningProcessForeground': '#369432',
'progressBar.background': '#0078d4',
'quickInput.background': '#222222',
'quickInput.foreground': '#cccccc',
'settings.dropdownBackground': '#313131',
'settings.dropdownBorder': '#3c3c3c',
'settings.headerForeground': '#ffffff',
'settings.modifiedItemIndicator': '#bb800966',
'sideBar.background': '#181818',
'sideBar.border': '#2b2b2b',
'sideBar.foreground': '#cccccc',
'sideBarSectionHeader.background': '#181818',
'sideBarSectionHeader.border': '#2b2b2b',
'sideBarSectionHeader.foreground': '#cccccc',
'sideBarTitle.foreground': '#cccccc',
'statusBar.background': '#181818',
'statusBar.border': '#2b2b2b',
'statusBar.debuggingBackground': '#0078d4',
'statusBar.debuggingForeground': '#ffffff',
'statusBar.focusBorder': '#0078d4',
'statusBar.foreground': '#cccccc',
'statusBar.noFolderBackground': '#1f1f1f',
'statusBarItem.focusBorder': '#0078d4',
'statusBarItem.prominentBackground': '#6e768166',
'statusBarItem.remoteBackground': '#0078d4',
'statusBarItem.remoteForeground': '#ffffff',
'tab.activeBackground': '#1f1f1f',
'tab.activeBorder': '#1f1f1f',
'tab.activeBorderTop': '#0078d4',
'tab.activeForeground': '#ffffff',
'tab.border': '#2b2b2b',
'tab.hoverBackground': '#1f1f1f',
'tab.inactiveBackground': '#181818',
'tab.inactiveForeground': '#9d9d9d',
'tab.lastPinnedBorder': '#cccccc33',
'tab.unfocusedActiveBorder': '#1f1f1f',
'tab.unfocusedActiveBorderTop': '#2b2b2b',
'tab.unfocusedHoverBackground': '#1f1f1f',
'terminal.foreground': '#cccccc',
'terminal.inactiveSelectionBackground': '#3a3d41',
'terminal.tab.activeBorder': '#0078d4',
'textBlockQuote.background': '#2b2b2b',
'textBlockQuote.border': '#616161',
'textCodeBlock.background': '#2b2b2b',
'textLink.activeForeground': '#4daafc',
'textLink.foreground': '#4daafc',
'textPreformat.background': '#3c3c3c',
'textPreformat.foreground': '#d0d0d0',
'textSeparator.foreground': '#21262d',
'titleBar.activeBackground': '#181818',
'titleBar.activeForeground': '#cccccc',
'titleBar.border': '#2b2b2b',
'titleBar.inactiveBackground': '#1f1f1f',
'titleBar.inactiveForeground': '#9d9d9d',
'welcomePage.progress.foreground': '#0078d4',
'welcomePage.tileBackground': '#2b2b2b',
'widget.border': '#313131',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'header',
settings: {
foreground: '#000080',
},
},
{
scope: 'comment',
settings: {
foreground: '#6A9955',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#569CD6',
},
},
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
],
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#646695',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'entity.name.tag.css',
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'invalid',
settings: {
foreground: '#F44747',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
foreground: '#569CD6',
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#569CD6',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'punctuation.definition.quote.begin.markdown',
settings: {
foreground: '#6A9955',
},
},
{
scope: 'punctuation.definition.list.begin.markdown',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'punctuation.definition.tag',
settings: {
foreground: '#808080',
},
},
{
scope: ['meta.preprocessor', 'entity.name.function.preprocessor'],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#569CD6',
},
},
{
scope: ['storage.modifier', 'keyword.operator.noexcept'],
settings: {
foreground: '#569CD6',
},
},
{
scope: ['string', 'meta.embedded.assembly'],
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.tag',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.value',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#D16969',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'keyword',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#D4D4D4',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#569CD6',
},
},
{
scope: [
'entity.name.function',
'support.function',
'support.constant.handlebars',
'source.powershell variable.other.member',
'entity.name.operator.custom-literal',
],
settings: {
foreground: '#DCDCAA',
},
},
{
scope: [
'support.class',
'support.type',
'entity.name.type',
'entity.name.namespace',
'entity.other.attribute',
'entity.name.scope-resolution',
'entity.name.class',
'storage.type.numeric.go',
'storage.type.byte.go',
'storage.type.boolean.go',
'storage.type.string.go',
'storage.type.uintptr.go',
'storage.type.error.go',
'storage.type.rune.go',
'storage.type.cs',
'storage.type.generic.cs',
'storage.type.modifier.cs',
'storage.type.variable.cs',
'storage.type.annotation.java',
'storage.type.generic.java',
'storage.type.java',
'storage.type.object.array.java',
'storage.type.primitive.array.java',
'storage.type.primitive.java',
'storage.type.token.java',
'storage.type.groovy',
'storage.type.annotation.groovy',
'storage.type.parameters.groovy',
'storage.type.generic.groovy',
'storage.type.object.array.groovy',
'storage.type.primitive.array.groovy',
'storage.type.primitive.groovy',
],
settings: {
foreground: '#4EC9B0',
},
},
{
scope: [
'meta.type.cast.expr',
'meta.type.new.expr',
'support.constant.math',
'support.constant.dom',
'support.constant.json',
'entity.other.inherited-class',
],
settings: {
foreground: '#4EC9B0',
},
},
{
scope: [
'keyword.control',
'source.cpp keyword.operator.new',
'keyword.operator.delete',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
'entity.name.operator',
],
settings: {
foreground: '#C586C0',
},
},
{
scope: [
'variable',
'meta.definition.variable.name',
'support.variable',
'entity.name.variable',
'constant.other.placeholder',
],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: ['variable.other.constant', 'variable.other.enummember'],
settings: {
foreground: '#4FC1FF',
},
},
{
scope: ['meta.object-literal.key'],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#CE9178',
},
},
{
scope: [
'punctuation.definition.group.regexp',
'punctuation.definition.group.assertion.regexp',
'punctuation.definition.character-class.regexp',
'punctuation.character.set.begin.regexp',
'punctuation.character.set.end.regexp',
'keyword.operator.negation.regexp',
'support.other.parenthesis.regexp',
],
settings: {
foreground: '#CE9178',
},
},
{
scope: [
'constant.character.character-class.regexp',
'constant.other.character-class.set.regexp',
'constant.other.character-class.regexp',
'constant.character.set.regexp',
],
settings: {
foreground: '#D16969',
},
},
{
scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'],
settings: {
foreground: '#DCDCAA',
},
},
{
scope: 'keyword.operator.quantifier.regexp',
settings: {
foreground: '#D7BA7D',
},
},
{
scope: ['constant.character', 'constant.other.option'],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'constant.character.escape',
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'entity.name.label',
settings: {
foreground: '#C8C8C8',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,21 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export { default as darkPlus } from 'shiki/themes/dark-plus.mjs';
export { default as lightPlus } from 'shiki/themes/light-plus.mjs';
export { default as monokai } from 'shiki/themes/monokai.mjs';
export { default as solarizedDark } from 'shiki/themes/solarized-dark.mjs';
export { default as solarizedLight } from 'shiki/themes/solarized-light.mjs';
export { abyss } from './abyss';
export { darkHC } from './dark-hc';
export { darkModern } from './dark-modern';
export { kimbieDark } from './kimbie-dark';
export { lightHC } from './light-hc';
export { lightModern } from './light-modern';
export { monokaiDim } from './monokai-dim';
export { quietLight } from './quiet-light';
export { red } from './red';
export { tomorrowNightBlue } from './tomorrow-night-blue';
export { vsDark } from './vs-dark';
export { vsLight } from './vs-light';
@@ -1,374 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const kimbieDark: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'activityBar.background': '#221a0f',
'activityBar.foreground': '#d3af86',
'badge.background': '#7f5d38',
'button.background': '#6e583b',
'dropdown.background': '#51412c',
'editor.background': '#221a0f',
'editor.foreground': '#d3af86',
'editor.lineHighlightBackground': '#5e452b',
'editor.selectionBackground': '#84613daa',
'editorCursor.foreground': '#d3af86',
'editorGroupHeader.tabsBackground': '#131510',
'editorHoverWidget.background': '#221a14',
'editorLineNumber.activeForeground': '#adadad',
'editorWhitespace.foreground': '#a57a4c',
'editorWidget.background': '#131510',
focusBorder: '#a57a4c',
'input.background': '#51412c',
'inputOption.activeBorder': '#a57a4c',
'inputValidation.errorBackground': '#5f0d0d',
'inputValidation.errorBorder': '#9d2f23',
'inputValidation.infoBackground': '#2b2a42',
'inputValidation.infoBorder': '#1b60a5',
'inputValidation.warningBackground': '#51412c',
'list.activeSelectionBackground': '#7c5021',
'list.highlightForeground': '#e3b583',
'list.hoverBackground': '#7c502166',
'list.inactiveSelectionBackground': '#645342',
'menu.background': '#362712',
'menu.foreground': '#cccccc',
'minimap.selectionHighlight': '#84613daa',
'peekView.border': '#5e452b',
'peekViewEditor.background': '#221a14',
'peekViewEditor.matchHighlightBackground': '#84613daa',
'peekViewResult.background': '#362712',
'peekViewTitle.background': '#362712',
'pickerGroup.border': '#e3b583',
'pickerGroup.foreground': '#e3b583',
'ports.iconRunningProcessForeground': '#369432',
'progressBar.background': '#7f5d38',
'quickInputList.focusBackground': '#7c5021aa',
'selection.background': '#84613daa',
'sideBar.background': '#362712',
'statusBar.background': '#423523',
'statusBar.debuggingBackground': '#423523',
'statusBar.noFolderBackground': '#423523',
'statusBarItem.remoteBackground': '#6e583b',
'tab.inactiveBackground': '#131510',
'tab.lastPinnedBorder': '#51412c',
'titleBar.activeBackground': '#423523',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#D3AF86',
},
},
{
scope: 'variable.parameter.function',
settings: {
foreground: '#D3AF86',
},
},
{
scope: ['comment', 'punctuation.definition.comment'],
settings: {
foreground: '#A57A4C',
},
},
{
scope: [
'punctuation.definition.string',
'punctuation.definition.variable',
'punctuation.definition.string',
'punctuation.definition.parameters',
'punctuation.definition.string',
'punctuation.definition.array',
],
settings: {
foreground: '#D3AF86',
},
},
{
scope: 'none',
settings: {
foreground: '#D3AF86',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#D3AF86',
},
},
{
scope: [
'keyword',
'keyword.control',
'keyword.operator.new.cpp',
'keyword.operator.delete.cpp',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
],
settings: {
foreground: '#98676A',
},
},
{
scope: 'variable',
settings: {
foreground: '#DC3958',
},
},
{
scope: ['entity.name.function', 'meta.require', 'support.function.any-method'],
settings: {
foreground: '#8AB1B0',
},
},
{
scope: [
'support.class',
'entity.name.class',
'entity.name.type',
'entity.name.namespace',
'entity.name.scope-resolution',
],
settings: {
foreground: '#F06431',
},
},
{
scope: 'keyword.other.special-method',
settings: {
foreground: '#8AB1B0',
},
},
{
scope: 'storage',
settings: {
foreground: '#98676A',
},
},
{
scope: 'support.function',
settings: {
foreground: '#7E602C',
},
},
{
scope: ['string', 'constant.other.symbol', 'entity.other.inherited-class'],
settings: {
foreground: '#889B4A',
},
},
{
scope: 'constant.numeric',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'none',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'none',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'constant',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#DC3958',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#F79A32',
},
},
{
scope: ['entity.other.attribute-name.id', 'punctuation.definition.entity'],
settings: {
foreground: '#8AB1B0',
},
},
{
scope: 'meta.selector',
settings: {
foreground: '#98676A',
},
},
{
scope: 'none',
settings: {
foreground: '#F79A32',
},
},
{
scope: ['markup.heading', 'markup.heading.setext', 'punctuation.definition.heading', 'entity.name.section'],
settings: {
foreground: '#8AB1B0',
fontStyle: 'bold',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#F79A32',
},
},
{
scope: ['markup.bold', 'punctuation.definition.bold'],
settings: {
foreground: '#F06431',
fontStyle: 'bold',
},
},
{
scope: ['markup.italic', 'punctuation.definition.italic'],
settings: {
foreground: '#98676A',
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#889B4A',
},
},
{
scope: 'string.other.link',
settings: {
foreground: '#DC3958',
},
},
{
scope: 'meta.link',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'markup.list',
settings: {
foreground: '#DC3958',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#F79A32',
},
},
{
scope: 'meta.separator',
settings: {
foreground: '#D3AF86',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#889B4A',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#DC3958',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#98676A',
},
},
{
scope: 'constant.other.color',
settings: {
foreground: '#7E602C',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#7E602C',
},
},
{
scope: 'constant.character.escape',
settings: {
foreground: '#7E602C',
},
},
{
scope: ['punctuation.section.embedded', 'variable.interpolation'],
settings: {
foreground: '#088649',
},
},
{
scope: 'invalid',
settings: {
foreground: '#DC3958',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,572 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const lightHC: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'light',
colors: {
'actionBar.toggledBackground': '#dddddd',
},
tokenColors: [
{
scope: ['meta.embedded', 'source.groovy.embedded', 'variable.legacy.builtin.python'],
settings: {
foreground: '#292929',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#062F4A',
},
},
{
scope: 'comment',
settings: {
foreground: '#515151',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#0F4A85',
},
},
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
],
settings: {
foreground: '#096D48',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'entity.name.selector',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#264F78',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'invalid',
settings: {
foreground: '#B5200D',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
foreground: '#000080',
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#0F4A85',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#096D48',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#5A5A5A',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#0451A5',
},
},
{
scope: ['punctuation.definition.quote.begin.markdown', 'punctuation.definition.list.begin.markdown'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'punctuation.definition.tag',
settings: {
foreground: '#0F4A85',
},
},
{
scope: ['meta.preprocessor', 'entity.name.function.preprocessor'],
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#B5200D',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#096D48',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'storage',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#0F4A85',
},
},
{
scope: ['storage.modifier', 'keyword.operator.noexcept'],
settings: {
foreground: '#0F4A85',
},
},
{
scope: ['string', 'meta.embedded.assembly'],
settings: {
foreground: '#0F4A85',
},
},
{
scope: [
'string.comment.buffered.block.pug',
'string.quoted.pug',
'string.interpolated.pug',
'string.unquoted.plain.in.yaml',
'string.unquoted.plain.out.yaml',
'string.unquoted.block.yaml',
'string.quoted.single.yaml',
'string.quoted.double.xml',
'string.quoted.single.xml',
'string.unquoted.cdata.xml',
'string.quoted.double.html',
'string.quoted.single.html',
'string.unquoted.html',
'string.quoted.single.handlebars',
'string.quoted.double.handlebars',
],
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#0F4A85',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#000000',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#0451A5',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#264F78',
},
},
{
scope: ['support.type.property-name.json'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'keyword',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#000000',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#096D48',
},
},
{
scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'],
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#096D48',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#000000',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#0F4A85',
},
},
{
scope: [
'entity.name.function',
'support.function',
'support.constant.handlebars',
'source.powershell variable.other.member',
'entity.name.operator.custom-literal',
],
settings: {
foreground: '#5E2CBC',
},
},
{
scope: [
'support.class',
'support.type',
'entity.name.type',
'entity.name.namespace',
'entity.other.attribute',
'entity.name.scope-resolution',
'entity.name.class',
'storage.type.numeric.go',
'storage.type.byte.go',
'storage.type.boolean.go',
'storage.type.string.go',
'storage.type.uintptr.go',
'storage.type.error.go',
'storage.type.rune.go',
'storage.type.cs',
'storage.type.generic.cs',
'storage.type.modifier.cs',
'storage.type.variable.cs',
'storage.type.annotation.java',
'storage.type.generic.java',
'storage.type.java',
'storage.type.object.array.java',
'storage.type.primitive.array.java',
'storage.type.primitive.java',
'storage.type.token.java',
'storage.type.groovy',
'storage.type.annotation.groovy',
'storage.type.parameters.groovy',
'storage.type.generic.groovy',
'storage.type.object.array.groovy',
'storage.type.primitive.array.groovy',
'storage.type.primitive.groovy',
],
settings: {
foreground: '#185E73',
},
},
{
scope: [
'meta.type.cast.expr',
'meta.type.new.expr',
'support.constant.math',
'support.constant.dom',
'support.constant.json',
'entity.other.inherited-class',
],
settings: {
foreground: '#185E73',
},
},
{
scope: [
'keyword.control',
'source.cpp keyword.operator.new',
'source.cpp keyword.operator.delete',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
'entity.name.operator',
],
settings: {
foreground: '#B5200D',
},
},
{
scope: [
'variable',
'meta.definition.variable.name',
'support.variable',
'entity.name.variable',
'constant.other.placeholder',
],
settings: {
foreground: '#001080',
},
},
{
scope: ['variable.other.constant', 'variable.other.enummember'],
settings: {
foreground: '#02715D',
},
},
{
scope: ['meta.object-literal.key'],
settings: {
foreground: '#001080',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#0451A5',
},
},
{
scope: [
'punctuation.definition.group.regexp',
'punctuation.definition.group.assertion.regexp',
'punctuation.definition.character-class.regexp',
'punctuation.character.set.begin.regexp',
'punctuation.character.set.end.regexp',
'keyword.operator.negation.regexp',
'support.other.parenthesis.regexp',
],
settings: {
foreground: '#D16969',
},
},
{
scope: [
'constant.character.character-class.regexp',
'constant.other.character-class.set.regexp',
'constant.other.character-class.regexp',
'constant.character.set.regexp',
],
settings: {
foreground: '#811F3F',
},
},
{
scope: 'keyword.operator.quantifier.regexp',
settings: {
foreground: '#000000',
},
},
{
scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'],
settings: {
foreground: '#EE0000',
},
},
{
scope: 'constant.character',
settings: {
foreground: '#0F4A85',
},
},
{
scope: 'constant.character.escape',
settings: {
foreground: '#EE0000',
},
},
{
scope: 'entity.name.label',
settings: {
foreground: '#000000',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#316BCD',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#800080',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#000000',
},
},
],
};
@@ -1,716 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const lightModern: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'light',
colors: {
'actionBar.toggledBackground': '#dddddd',
'activityBar.activeBorder': '#005fb8',
'activityBar.background': '#f8f8f8',
'activityBar.border': '#e5e5e5',
'activityBar.foreground': '#1f1f1f',
'activityBar.inactiveForeground': '#616161',
'activityBarBadge.background': '#005fb8',
'activityBarBadge.foreground': '#ffffff',
'badge.background': '#cccccc',
'badge.foreground': '#3b3b3b',
'button.background': '#005fb8',
'button.border': '#0000001a',
'button.foreground': '#ffffff',
'button.hoverBackground': '#0258a8',
'button.secondaryBackground': '#e5e5e5',
'button.secondaryForeground': '#3b3b3b',
'button.secondaryHoverBackground': '#cccccc',
'chat.slashCommandBackground': '#d2ecff',
'chat.slashCommandForeground': '#306ca2',
'checkbox.background': '#f8f8f8',
'checkbox.border': '#cecece',
descriptionForeground: '#3b3b3b',
'dropdown.background': '#ffffff',
'dropdown.border': '#cecece',
'dropdown.foreground': '#3b3b3b',
'dropdown.listBackground': '#ffffff',
'editor.background': '#ffffff',
'editor.foreground': '#3b3b3b',
'editor.inactiveSelectionBackground': '#e5ebf1',
'editor.selectionHighlightBackground': '#add6ff80',
'editorGroup.border': '#e5e5e5',
'editorGroupHeader.tabsBackground': '#f8f8f8',
'editorGroupHeader.tabsBorder': '#e5e5e5',
'editorGutter.addedBackground': '#2ea043',
'editorGutter.deletedBackground': '#f85149',
'editorGutter.modifiedBackground': '#005fb8',
'editorIndentGuide.activeBackground1': '#939393',
'editorIndentGuide.background1': '#d3d3d3',
'editorLineNumber.activeForeground': '#171184',
'editorLineNumber.foreground': '#6e7681',
'editorOverviewRuler.border': '#e5e5e5',
'editorSuggestWidget.background': '#f8f8f8',
'editorWidget.background': '#f8f8f8',
errorForeground: '#f85149',
focusBorder: '#005fb8',
foreground: '#3b3b3b',
'icon.foreground': '#3b3b3b',
'input.background': '#ffffff',
'input.border': '#cecece',
'input.foreground': '#3b3b3b',
'input.placeholderForeground': '#868686',
'inputOption.activeBackground': '#bed6ed',
'inputOption.activeBorder': '#005fb8',
'inputOption.activeForeground': '#000000',
'keybindingLabel.foreground': '#3b3b3b',
'list.activeSelectionBackground': '#e8e8e8',
'list.activeSelectionForeground': '#000000',
'list.activeSelectionIconForeground': '#000000',
'list.focusAndSelectionOutline': '#005fb8',
'list.hoverBackground': '#f2f2f2',
'menu.border': '#cecece',
'notebook.cellBorderColor': '#e5e5e5',
'notebook.selectedCellBackground': '#c8ddf150',
'notificationCenterHeader.background': '#ffffff',
'notificationCenterHeader.foreground': '#3b3b3b',
'notifications.background': '#ffffff',
'notifications.border': '#e5e5e5',
'notifications.foreground': '#3b3b3b',
'panel.background': '#f8f8f8',
'panel.border': '#e5e5e5',
'panelInput.border': '#e5e5e5',
'panelTitle.activeBorder': '#005fb8',
'panelTitle.activeForeground': '#3b3b3b',
'panelTitle.inactiveForeground': '#3b3b3b',
'peekViewEditor.matchHighlightBackground': '#bb800966',
'peekViewResult.background': '#ffffff',
'peekViewResult.matchHighlightBackground': '#bb800966',
'pickerGroup.border': '#e5e5e5',
'pickerGroup.foreground': '#8b949e',
'ports.iconRunningProcessForeground': '#369432',
'progressBar.background': '#005fb8',
'quickInput.background': '#f8f8f8',
'quickInput.foreground': '#3b3b3b',
'searchEditor.textInputBorder': '#cecece',
'settings.dropdownBackground': '#ffffff',
'settings.dropdownBorder': '#cecece',
'settings.headerForeground': '#1f1f1f',
'settings.modifiedItemIndicator': '#bb800966',
'settings.numberInputBorder': '#cecece',
'settings.textInputBorder': '#cecece',
'sideBar.background': '#f8f8f8',
'sideBar.border': '#e5e5e5',
'sideBar.foreground': '#3b3b3b',
'sideBarSectionHeader.background': '#f8f8f8',
'sideBarSectionHeader.border': '#e5e5e5',
'sideBarSectionHeader.foreground': '#3b3b3b',
'sideBarTitle.foreground': '#3b3b3b',
'statusBar.background': '#f8f8f8',
'statusBar.border': '#e5e5e5',
'statusBar.debuggingBackground': '#fd716c',
'statusBar.debuggingForeground': '#000000',
'statusBar.focusBorder': '#005fb8',
'statusBar.foreground': '#3b3b3b',
'statusBar.noFolderBackground': '#f8f8f8',
'statusBarItem.errorBackground': '#c72e0f',
'statusBarItem.focusBorder': '#005fb8',
'statusBarItem.prominentBackground': '#6e768166',
'statusBarItem.remoteBackground': '#005fb8',
'statusBarItem.remoteForeground': '#ffffff',
'tab.activeBackground': '#ffffff',
'tab.activeBorder': '#f8f8f8',
'tab.activeBorderTop': '#005fb8',
'tab.activeForeground': '#3b3b3b',
'tab.border': '#e5e5e5',
'tab.hoverBackground': '#ffffff',
'tab.inactiveBackground': '#f8f8f8',
'tab.inactiveForeground': '#868686',
'tab.lastPinnedBorder': '#d4d4d4',
'tab.unfocusedActiveBorder': '#f8f8f8',
'tab.unfocusedActiveBorderTop': '#e5e5e5',
'tab.unfocusedHoverBackground': '#f8f8f8',
'terminal.foreground': '#3b3b3b',
'terminal.inactiveSelectionBackground': '#e5ebf1',
'terminal.tab.activeBorder': '#005fb8',
'terminalCursor.foreground': '#005fb8',
'textBlockQuote.background': '#f8f8f8',
'textBlockQuote.border': '#e5e5e5',
'textCodeBlock.background': '#f8f8f8',
'textLink.activeForeground': '#005fb8',
'textLink.foreground': '#005fb8',
'textPreformat.background': '#0000001f',
'textPreformat.foreground': '#3b3b3b',
'textSeparator.foreground': '#21262d',
'titleBar.activeBackground': '#f8f8f8',
'titleBar.activeForeground': '#1e1e1e',
'titleBar.border': '#e5e5e5',
'titleBar.inactiveBackground': '#f8f8f8',
'titleBar.inactiveForeground': '#8b949e',
'welcomePage.tileBackground': '#f3f3f3',
'widget.border': '#e5e5e5',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#000000',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#000080',
},
},
{
scope: 'comment',
settings: {
foreground: '#008000',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#0000FF',
},
},
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
],
settings: {
foreground: '#098658',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#800000',
},
},
{
scope: 'entity.name.selector',
settings: {
foreground: '#800000',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#E50000',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#800000',
},
},
{
scope: 'invalid',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
foreground: '#000080',
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#800000',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#098658',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#A31515',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#0451A5',
},
},
{
scope: ['punctuation.definition.quote.begin.markdown', 'punctuation.definition.list.begin.markdown'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#800000',
},
},
{
scope: 'punctuation.definition.tag',
settings: {
foreground: '#800000',
},
},
{
scope: ['meta.preprocessor', 'entity.name.function.preprocessor'],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#A31515',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#098658',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'storage',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#0000FF',
},
},
{
scope: ['storage.modifier', 'keyword.operator.noexcept'],
settings: {
foreground: '#0000FF',
},
},
{
scope: ['string', 'meta.embedded.assembly'],
settings: {
foreground: '#A31515',
},
},
{
scope: [
'string.comment.buffered.block.pug',
'string.quoted.pug',
'string.interpolated.pug',
'string.unquoted.plain.in.yaml',
'string.unquoted.plain.out.yaml',
'string.unquoted.block.yaml',
'string.quoted.single.yaml',
'string.quoted.double.xml',
'string.quoted.single.xml',
'string.unquoted.cdata.xml',
'string.quoted.double.html',
'string.quoted.single.html',
'string.unquoted.html',
'string.quoted.single.handlebars',
'string.quoted.double.handlebars',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#000000',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#0451A5',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#E50000',
},
},
{
scope: ['support.type.property-name.json'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'keyword',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#000000',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#098658',
},
},
{
scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'],
settings: {
foreground: '#800000',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#098658',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#000000',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#0000FF',
},
},
{
scope: [
'entity.name.function',
'support.function',
'support.constant.handlebars',
'source.powershell variable.other.member',
'entity.name.operator.custom-literal',
],
settings: {
foreground: '#795E26',
},
},
{
scope: [
'support.class',
'support.type',
'entity.name.type',
'entity.name.namespace',
'entity.other.attribute',
'entity.name.scope-resolution',
'entity.name.class',
'storage.type.numeric.go',
'storage.type.byte.go',
'storage.type.boolean.go',
'storage.type.string.go',
'storage.type.uintptr.go',
'storage.type.error.go',
'storage.type.rune.go',
'storage.type.cs',
'storage.type.generic.cs',
'storage.type.modifier.cs',
'storage.type.variable.cs',
'storage.type.annotation.java',
'storage.type.generic.java',
'storage.type.java',
'storage.type.object.array.java',
'storage.type.primitive.array.java',
'storage.type.primitive.java',
'storage.type.token.java',
'storage.type.groovy',
'storage.type.annotation.groovy',
'storage.type.parameters.groovy',
'storage.type.generic.groovy',
'storage.type.object.array.groovy',
'storage.type.primitive.array.groovy',
'storage.type.primitive.groovy',
],
settings: {
foreground: '#267F99',
},
},
{
scope: [
'meta.type.cast.expr',
'meta.type.new.expr',
'support.constant.math',
'support.constant.dom',
'support.constant.json',
'entity.other.inherited-class',
],
settings: {
foreground: '#267F99',
},
},
{
scope: [
'keyword.control',
'source.cpp keyword.operator.new',
'source.cpp keyword.operator.delete',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
'entity.name.operator',
],
settings: {
foreground: '#AF00DB',
},
},
{
scope: [
'variable',
'meta.definition.variable.name',
'support.variable',
'entity.name.variable',
'constant.other.placeholder',
],
settings: {
foreground: '#001080',
},
},
{
scope: ['variable.other.constant', 'variable.other.enummember'],
settings: {
foreground: '#0070C1',
},
},
{
scope: ['meta.object-literal.key'],
settings: {
foreground: '#001080',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#0451A5',
},
},
{
scope: [
'punctuation.definition.group.regexp',
'punctuation.definition.group.assertion.regexp',
'punctuation.definition.character-class.regexp',
'punctuation.character.set.begin.regexp',
'punctuation.character.set.end.regexp',
'keyword.operator.negation.regexp',
'support.other.parenthesis.regexp',
],
settings: {
foreground: '#D16969',
},
},
{
scope: [
'constant.character.character-class.regexp',
'constant.other.character-class.set.regexp',
'constant.other.character-class.regexp',
'constant.character.set.regexp',
],
settings: {
foreground: '#811F3F',
},
},
{
scope: 'keyword.operator.quantifier.regexp',
settings: {
foreground: '#000000',
},
},
{
scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'],
settings: {
foreground: '#EE0000',
},
},
{
scope: ['constant.character', 'constant.other.option'],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'constant.character.escape',
settings: {
foreground: '#EE0000',
},
},
{
scope: 'entity.name.label',
settings: {
foreground: '#000000',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#000000',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#316BCD',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#800080',
},
},
],
};
@@ -1,573 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const monokaiDim: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'activityBar.background': '#353535',
'activityBar.foreground': '#ffffff',
'activityBarBadge.background': '#3655b5',
'button.background': '#565656',
'dropdown.background': '#525252',
'editor.background': '#1e1e1e',
'editor.foreground': '#c5c8c6',
'editor.lineHighlightBackground': '#303030',
'editor.selectionBackground': '#676b7180',
'editor.selectionHighlightBackground': '#575b6180',
'editor.wordHighlightBackground': '#4747a180',
'editor.wordHighlightStrongBackground': '#6767ce80',
'editorCursor.foreground': '#c07020',
'editorGroupHeader.tabsBackground': '#282828',
'editorIndentGuide.activeBackground': '#707057',
'editorIndentGuide.background': '#505037',
'editorLineNumber.activeForeground': '#949494',
'editorWhitespace.foreground': '#505037',
focusBorder: '#3655b5',
'inputOption.activeBorder': '#3655b5',
'list.activeSelectionBackground': '#707070',
'list.highlightForeground': '#e58520',
'list.hoverBackground': '#444444',
'list.inactiveSelectionBackground': '#4e4e4e',
'menu.background': '#272727',
'menu.foreground': '#cccccc',
'minimap.selectionHighlight': '#676b7180',
'panelTitle.activeForeground': '#ffffff',
'peekView.border': '#3655b5',
'pickerGroup.foreground': '#b0b0b0',
'ports.iconRunningProcessForeground': '#cccccc',
'quickInputList.focusBackground': '#707070',
'sideBar.background': '#272727',
'sideBarSectionHeader.background': '#505050',
'statusBar.background': '#505050',
'statusBar.debuggingBackground': '#505050',
'statusBar.noFolderBackground': '#505050',
'statusBarItem.remoteBackground': '#3655b5',
'tab.border': '#303030',
'tab.inactiveBackground': '#404040',
'tab.inactiveForeground': '#d8d8d8',
'tab.lastPinnedBorder': '#505050',
'terminal.ansiBlack': '#1e1e1e',
'terminal.ansiBlue': '#6a7ec8',
'terminal.ansiBrightBlack': '#666666',
'terminal.ansiBrightBlue': '#819aff',
'terminal.ansiBrightCyan': '#66d9ef',
'terminal.ansiBrightGreen': '#a6e22e',
'terminal.ansiBrightMagenta': '#ae81ff',
'terminal.ansiBrightRed': '#f92672',
'terminal.ansiBrightWhite': '#f8f8f2',
'terminal.ansiBrightYellow': '#e2e22e',
'terminal.ansiCyan': '#56adbc',
'terminal.ansiGreen': '#86b42b',
'terminal.ansiMagenta': '#8c6bc8',
'terminal.ansiRed': '#c4265e',
'terminal.ansiWhite': '#e3e3dd',
'terminal.ansiYellow': '#b3b42b',
'terminal.inactiveSelectionBackground': '#676b7140',
'titleBar.activeBackground': '#505050',
},
tokenColors: [
{
scope: ['meta.embedded', 'source.groovy.embedded', 'variable.legacy.builtin.python'],
settings: {
foreground: '#C5C8C6',
},
},
{
scope: 'comment',
settings: {
foreground: '#9A9B99',
fontStyle: '',
},
},
{
scope: 'string',
settings: {
foreground: '#9AA83A',
fontStyle: '',
},
},
{
scope: 'string source',
settings: {
foreground: '#D08442',
fontStyle: '',
},
},
{
scope: 'constant.numeric',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#408080',
fontStyle: '',
},
},
{
scope: 'constant.character, constant.other',
settings: {
foreground: '#8080FF',
fontStyle: '',
},
},
{
scope: 'keyword',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'support',
settings: {
foreground: '#C7444A',
fontStyle: '',
},
},
{
scope: 'storage',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution',
settings: {
foreground: '#9B0000',
fontStyle: '',
},
},
{
scope: 'entity.other.inherited-class',
settings: {
foreground: '#C7444A',
fontStyle: '',
},
},
{
scope: 'entity.name.function',
settings: {
foreground: '#CE6700',
fontStyle: '',
},
},
{
scope: 'variable.parameter',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'support.function',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'keyword',
settings: {
foreground: '#676867',
fontStyle: '',
},
},
{
scope: 'variable.other, variable.js, punctuation.separator.variable',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html',
settings: {
foreground: '#008200',
fontStyle: '',
},
},
{
scope: 'invalid',
settings: {
foreground: '#FF0B00',
fontStyle: '',
},
},
{
scope: 'variable.other.php, variable.other.normal',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'meta.function-call.object',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'variable.other.property',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: [
'keyword.control',
'keyword.operator.new.cpp',
'keyword.operator.delete.cpp',
'keyword.other.using',
'keyword.other.directive.using',
'keyword.other.operator',
],
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'meta.tag',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'meta.doctype, meta.tag.sgml-declaration.doctype, meta.tag.sgml.doctype',
settings: {
foreground: '#9AA83A',
fontStyle: '',
},
},
{
scope: 'meta.tag.inline source, text.html.php.source',
settings: {
foreground: '#9AA83A',
fontStyle: '',
},
},
{
scope: 'meta.tag.other, entity.name.tag.style, entity.name.tag.script, meta.tag.block.script, source.js.embedded punctuation.definition.tag.html, source.css.embedded punctuation.definition.tag.html',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'entity.other.attribute-name, meta.tag punctuation.definition.string',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'meta.tag string -source -punctuation, text source text meta.tag string -punctuation',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'meta.toc-list.id',
settings: {
foreground: '#9AA83A',
},
},
{
scope: 'string.quoted.double.html, punctuation.definition.string.begin.html, punctuation.definition.string.end.html, punctuation.definition.string.end.html source, string.quoted.double.html source',
settings: {
foreground: '#9AA83A',
fontStyle: '',
},
},
{
scope: 'punctuation.definition.tag.html, punctuation.definition.tag.begin, punctuation.definition.tag.end',
settings: {
foreground: '#6089B4',
fontStyle: '',
},
},
{
scope: 'meta.selector.css entity.other.attribute-name.id',
settings: {
foreground: '#9872A2',
fontStyle: '',
},
},
{
scope: 'support.type.property-name.css',
settings: {
foreground: '#676867',
fontStyle: '',
},
},
{
scope: 'meta.property-group support.constant.property-value.css, meta.property-value support.constant.property-value.css',
settings: {
foreground: '#C7444A',
fontStyle: '',
},
},
{
scope: 'variable.language.js',
settings: {
foreground: '#CC555A',
},
},
{
scope: ['punctuation.definition.template-expression', 'punctuation.section.embedded.coffee'],
settings: {
foreground: '#D08442',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#C5C8C6',
},
},
{
scope: 'meta.function-call.object.php',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'punctuation.definition.string.end.php, punctuation.definition.string.begin.php',
settings: {
foreground: '#9AA83A',
},
},
{
scope: 'source.php.embedded.line.html',
settings: {
foreground: '#676867',
},
},
{
scope: 'punctuation.section.embedded.begin.php, punctuation.section.embedded.end.php',
settings: {
foreground: '#D08442',
fontStyle: '',
},
},
{
scope: 'constant.other.symbol.ruby',
settings: {
foreground: '#9AA83A',
fontStyle: '',
},
},
{
scope: 'variable.language.ruby',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'keyword.other.special-method.ruby',
settings: {
foreground: '#D9B700',
fontStyle: '',
},
},
{
scope: ['punctuation.section.embedded.begin.ruby', 'punctuation.section.embedded.end.ruby'],
settings: {
foreground: '#D08442',
},
},
{
scope: 'keyword.other.DML.sql',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'meta.diff, meta.diff.header',
settings: {
foreground: '#E0EDDD',
fontStyle: 'italic',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#DC322F',
fontStyle: '',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#CB4B16',
fontStyle: '',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#219186',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#9872A2',
},
},
{
scope: 'markup.list',
settings: {
foreground: '#9AA83A',
},
},
{
scope: 'markup.bold, markup.italic',
settings: {
foreground: '#6089B4',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#FF0080',
fontStyle: '',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#D0B344',
},
},
{
scope: 'markup.heading.setext',
settings: {
foreground: '#D0B344',
fontStyle: '',
},
},
{
scope: 'markup.heading.markdown',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.quote.markdown',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.bold.markdown',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'string.other.link.title.markdown,string.other.link.description.markdown',
settings: {
foreground: '#AE81FF',
},
},
{
scope: 'markup.underline.link.markdown,markup.underline.link.image.markdown',
settings: {},
},
{
scope: 'markup.italic.markdown',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.list.unnumbered.markdown, markup.list.numbered.markdown',
settings: {},
},
{
scope: ['punctuation.definition.list.begin.markdown'],
settings: {},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#C7444A',
},
},
],
};
@@ -1,465 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const quietLight: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'light',
colors: {
'activityBar.background': '#ededf5',
'activityBar.foreground': '#705697',
'activityBarBadge.background': '#705697',
'badge.background': '#705697aa',
'button.background': '#705697',
'dropdown.background': '#f5f5f5',
'editor.background': '#f5f5f5',
'editor.findMatchBackground': '#bf9cac',
'editor.findMatchHighlightBackground': '#edc9d899',
'editor.lineHighlightBackground': '#e4f6d4',
'editor.selectionBackground': '#c9d0d9',
'editorCursor.foreground': '#54494b',
'editorGroup.dropBackground': '#c9d0d988',
'editorIndentGuide.activeBackground': '#777777b0',
'editorIndentGuide.background': '#aaaaaa60',
'editorLineNumber.activeForeground': '#9769dc',
'editorLineNumber.foreground': '#6d705b',
'editorWhitespace.foreground': '#aaaaaa',
errorForeground: '#f1897f',
focusBorder: '#9769dc',
'inputOption.activeBorder': '#adafb7',
'inputValidation.errorBackground': '#ffeaea',
'inputValidation.errorBorder': '#f1897f',
'inputValidation.infoBackground': '#f2fcff',
'inputValidation.infoBorder': '#4ec1e5',
'inputValidation.warningBackground': '#fffee2',
'inputValidation.warningBorder': '#ffe055',
'list.activeSelectionBackground': '#c4d9b1',
'list.activeSelectionForeground': '#6c6c6c',
'list.highlightForeground': '#9769dc',
'list.hoverBackground': '#e0e0e0',
'list.inactiveSelectionBackground': '#d3dbcd',
'minimap.selectionHighlight': '#c9d0d9',
'panel.background': '#f5f5f5',
'peekView.border': '#705697',
'peekViewEditor.background': '#f2f8fc',
'peekViewEditor.matchHighlightBackground': '#c2dfe3',
'peekViewResult.background': '#f2f8fc',
'peekViewResult.matchHighlightBackground': '#93c6d6',
'peekViewTitle.background': '#f2f8fc',
'pickerGroup.border': '#749351',
'pickerGroup.foreground': '#a6b39b',
'ports.iconRunningProcessForeground': '#749351',
'progressBar.background': '#705697',
'quickInputList.focusBackground': '#cadeb9',
'selection.background': '#c9d0d9',
'sideBar.background': '#f2f2f2',
'sideBarSectionHeader.background': '#ede8ef',
'statusBar.background': '#705697',
'statusBar.debuggingBackground': '#705697',
'statusBar.noFolderBackground': '#705697',
'statusBarItem.remoteBackground': '#4e3c69',
'tab.lastPinnedBorder': '#c9d0d9',
'titleBar.activeBackground': '#c4b7d7',
'walkThrough.embeddedEditorBackground': '#00000014',
'welcomePage.tileBackground': '#f0f0f7',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#333333',
},
},
{
scope: ['comment', 'punctuation.definition.comment'],
settings: {
foreground: '#AAAAAA',
fontStyle: 'italic',
},
},
{
scope: 'comment.block.preprocessor',
settings: {
foreground: '#AAAAAA',
fontStyle: '',
},
},
{
scope: [
'comment.documentation',
'comment.block.documentation',
'comment.block.documentation punctuation.definition.comment ',
],
settings: {
foreground: '#448C27',
},
},
{
scope: 'invalid',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'invalid.illegal',
settings: {
foreground: '#660000',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#777777',
},
},
{
scope: ['keyword', 'storage'],
settings: {
foreground: '#4B69C6',
},
},
{
scope: ['storage.type', 'support.type'],
settings: {
foreground: '#7A3E9D',
},
},
{
scope: ['constant.language', 'support.constant', 'variable.language'],
settings: {
foreground: '#9C5D27',
},
},
{
scope: ['variable', 'support.variable'],
settings: {
foreground: '#7A3E9D',
},
},
{
scope: ['entity.name.function', 'support.function'],
settings: {
foreground: '#AA3731',
fontStyle: 'bold',
},
},
{
scope: [
'entity.name.type',
'entity.name.namespace',
'entity.name.scope-resolution',
'entity.other.inherited-class',
'support.class',
],
settings: {
foreground: '#7A3E9D',
fontStyle: 'bold',
},
},
{
scope: 'entity.name.exception',
settings: {
foreground: '#660000',
},
},
{
scope: 'entity.name.section',
settings: {
fontStyle: 'bold',
},
},
{
scope: ['constant.numeric', 'constant.character', 'constant'],
settings: {
foreground: '#9C5D27',
},
},
{
scope: 'string',
settings: {
foreground: '#448C27',
},
},
{
scope: 'constant.character.escape',
settings: {
foreground: '#777777',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#4B69C6',
},
},
{
scope: 'constant.other.symbol',
settings: {
foreground: '#9C5D27',
},
},
{
scope: 'punctuation',
settings: {
foreground: '#777777',
},
},
{
scope: [
'meta.tag.sgml.doctype',
'meta.tag.sgml.doctype string',
'meta.tag.sgml.doctype entity.name.tag',
'meta.tag.sgml punctuation.definition.tag.html',
],
settings: {
foreground: '#AAAAAA',
},
},
{
scope: [
'meta.tag',
'punctuation.definition.tag.html',
'punctuation.definition.tag.begin.html',
'punctuation.definition.tag.end.html',
],
settings: {
foreground: '#91B3E0',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#4B69C6',
},
},
{
scope: ['meta.tag entity.other.attribute-name', 'entity.other.attribute-name.html'],
settings: {
foreground: '#8190A0',
fontStyle: 'italic',
},
},
{
scope: ['constant.character.entity', 'punctuation.definition.entity'],
settings: {
foreground: '#9C5D27',
},
},
{
scope: ['meta.selector', 'meta.selector entity', 'meta.selector entity punctuation', 'entity.name.tag.css'],
settings: {
foreground: '#7A3E9D',
},
},
{
scope: ['meta.property-name', 'support.type.property-name'],
settings: {
foreground: '#9C5D27',
},
},
{
scope: ['meta.property-value', 'meta.property-value constant.other', 'support.constant.property-value'],
settings: {
foreground: '#448C27',
},
},
{
scope: 'keyword.other.important',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#000000',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#000000',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.error',
settings: {
foreground: '#660000',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#000000',
},
},
{
scope: 'meta.link',
settings: {
foreground: '#4B69C6',
},
},
{
scope: ['markup.output', 'markup.raw'],
settings: {
foreground: '#777777',
},
},
{
scope: 'markup.prompt',
settings: {
foreground: '#777777',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#AA3731',
},
},
{
scope: 'markup.bold',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.traceback',
settings: {
foreground: '#660000',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#7A3E9D',
},
},
{
scope: 'markup.list',
settings: {
foreground: '#4B69C6',
},
},
{
scope: ['markup.bold', 'markup.italic'],
settings: {
foreground: '#448C27',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#9C5D27',
fontStyle: '',
},
},
{
scope: ['meta.diff.range', 'meta.diff.index', 'meta.separator'],
settings: {
foreground: '#434343',
},
},
{
scope: ['meta.diff.header.from-file', 'punctuation.definition.from-file.diff'],
settings: {
foreground: '#4B69C6',
},
},
{
scope: ['meta.diff.header.to-file', 'punctuation.definition.to-file.diff'],
settings: {
foreground: '#4B69C6',
},
},
{
scope: 'markup.deleted.diff',
settings: {
foreground: '#C73D20',
},
},
{
scope: 'markup.changed.diff',
settings: {
foreground: '#9C5D27',
},
},
{
scope: 'markup.inserted.diff',
settings: {
foreground: '#448C27',
},
},
{
scope: [
'punctuation.definition.tag.js',
'punctuation.definition.tag.begin.js',
'punctuation.definition.tag.end.js',
],
settings: {
foreground: '#91B3E0',
},
},
{
scope: 'meta.jsx.children.js',
settings: {
foreground: '#333333',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#000000',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#316BCD',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#800080',
},
},
],
};
@@ -1,376 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const red: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'activityBar.background': '#580000',
'badge.background': '#cc3333',
'button.background': '#883333',
'debugToolBar.background': '#660000',
'dropdown.background': '#580000',
'editor.background': '#390000',
'editor.foreground': '#f8f8f8',
'editor.hoverHighlightBackground': '#ff000044',
'editor.lineHighlightBackground': '#ff000033',
'editor.selectionBackground': '#750000',
'editor.selectionHighlightBackground': '#f5500039',
'editorCursor.foreground': '#970000',
'editorGroup.border': '#ff666633',
'editorGroupHeader.tabsBackground': '#330000',
'editorHoverWidget.background': '#300000',
'editorLineNumber.activeForeground': '#ffbbbb88',
'editorLineNumber.foreground': '#ff777788',
'editorLink.activeForeground': '#ffd0aa',
'editorSuggestWidget.background': '#300000',
'editorSuggestWidget.border': '#220000',
'editorWhitespace.foreground': '#c10000',
'editorWidget.background': '#300000',
errorForeground: '#ffeaea',
'extensionButton.prominentBackground': '#cc3333',
'extensionButton.prominentHoverBackground': '#cc333388',
focusBorder: '#ff6666aa',
'input.background': '#580000',
'inputOption.activeBorder': '#cc0000',
'inputValidation.infoBackground': '#550000',
'inputValidation.infoBorder': '#db7e58',
'list.activeSelectionBackground': '#880000',
'list.dropBackground': '#662222',
'list.highlightForeground': '#ff4444',
'list.hoverBackground': '#800000',
'list.inactiveSelectionBackground': '#770000',
'minimap.selectionHighlight': '#750000',
'peekView.border': '#ff000044',
'peekViewEditor.background': '#300000',
'peekViewResult.background': '#400000',
'peekViewTitle.background': '#550000',
'pickerGroup.border': '#ff000033',
'pickerGroup.foreground': '#cc9999',
'ports.iconRunningProcessForeground': '#db7e58',
'progressBar.background': '#cc3333',
'quickInputList.focusBackground': '#660000',
'selection.background': '#ff777788',
'sideBar.background': '#330000',
'statusBar.background': '#700000',
'statusBar.noFolderBackground': '#700000',
'statusBarItem.remoteBackground': '#cc3333',
'tab.activeBackground': '#490000',
'tab.inactiveBackground': '#300a0a',
'tab.lastPinnedBorder': '#ff000044',
'titleBar.activeBackground': '#770000',
'titleBar.inactiveBackground': '#772222',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#F8F8F8',
},
},
{
scope: 'comment',
settings: {
foreground: '#E7C0C0',
fontStyle: 'italic',
},
},
{
scope: 'constant',
settings: {
foreground: '#994646',
fontStyle: '',
},
},
{
scope: 'keyword',
settings: {
foreground: '#F12727',
fontStyle: '',
},
},
{
scope: 'entity',
settings: {
foreground: '#FEC758',
fontStyle: '',
},
},
{
scope: 'storage',
settings: {
foreground: '#FF6262',
fontStyle: 'bold',
},
},
{
scope: 'string',
settings: {
foreground: '#CD8D8D',
fontStyle: '',
},
},
{
scope: 'support',
settings: {
foreground: '#9DF39F',
fontStyle: '',
},
},
{
scope: 'variable',
settings: {
foreground: '#FB9A4B',
fontStyle: 'italic',
},
},
{
scope: 'invalid',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'entity.other.inherited-class',
settings: {
foreground: '#AA5507',
fontStyle: 'underline',
},
},
{
scope: 'constant.character',
settings: {
foreground: '#EC0D1E',
},
},
{
scope: ['string constant', 'constant.character.escape'],
settings: {
foreground: '#FFE862',
fontStyle: '',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#FFB454',
},
},
{
scope: 'string variable',
settings: {
foreground: '#EDEF7D',
},
},
{
scope: 'support.function',
settings: {
foreground: '#FFB454',
fontStyle: '',
},
},
{
scope: ['support.constant', 'support.variable'],
settings: {
foreground: '#EB939A',
fontStyle: '',
},
},
{
scope: [
'declaration.sgml.html declaration.doctype',
'declaration.sgml.html declaration.doctype entity',
'declaration.sgml.html declaration.doctype string',
'declaration.xml-processing',
'declaration.xml-processing entity',
'declaration.xml-processing string',
],
settings: {
foreground: '#73817D',
fontStyle: '',
},
},
{
scope: ['declaration.tag', 'declaration.tag entity', 'meta.tag', 'meta.tag entity'],
settings: {
foreground: '#EC0D1E',
fontStyle: '',
},
},
{
scope: 'meta.selector.css entity.name.tag',
settings: {
foreground: '#AA5507',
fontStyle: '',
},
},
{
scope: 'meta.selector.css entity.other.attribute-name.id',
settings: {
foreground: '#FEC758',
},
},
{
scope: 'meta.selector.css entity.other.attribute-name.class',
settings: {
foreground: '#41A83E',
fontStyle: '',
},
},
{
scope: 'support.type.property-name.css',
settings: {
foreground: '#96DD3B',
fontStyle: '',
},
},
{
scope: [
'meta.property-group support.constant.property-value.css',
'meta.property-value support.constant.property-value.css',
],
settings: {
foreground: '#FFE862',
fontStyle: 'italic',
},
},
{
scope: ['meta.property-value support.constant.named-color.css', 'meta.property-value constant'],
settings: {
foreground: '#FFE862',
fontStyle: '',
},
},
{
scope: 'meta.preprocessor.at-rule keyword.control.at-rule',
settings: {
foreground: '#FD6209',
},
},
{
scope: 'meta.constructor.argument.css',
settings: {
foreground: '#EC9799',
fontStyle: '',
},
},
{
scope: ['meta.diff', 'meta.diff.header'],
settings: {
foreground: '#F8F8F8',
fontStyle: 'italic',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#EC9799',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#F8F8F8',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#41A83E',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#F12727',
},
},
{
scope: 'markup.list',
settings: {
foreground: '#FF6262',
},
},
{
scope: ['markup.bold', 'markup.italic'],
settings: {
foreground: '#FB9A4B',
},
},
{
scope: 'markup.bold',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#CD8D8D',
fontStyle: '',
},
},
{
scope: ['markup.heading', 'markup.heading.setext', 'punctuation.definition.heading', 'entity.name.section'],
settings: {
foreground: '#FEC758',
fontStyle: 'bold',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
'.format.placeholder',
],
settings: {
foreground: '#EC0D1E',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,265 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const tomorrowNightBlue: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'activityBar.background': '#001733',
'badge.background': '#bbdaffcc',
'badge.foreground': '#001733',
'debugToolBar.background': '#001c40',
'dropdown.background': '#001733',
'editor.background': '#002451',
'editor.foreground': '#ffffff',
'editor.lineHighlightBackground': '#00346e',
'editor.selectionBackground': '#003f8e',
'editorCursor.foreground': '#ffffff',
'editorGroup.border': '#404f7d',
'editorGroup.dropBackground': '#25375daa',
'editorGroupHeader.tabsBackground': '#001733',
'editorHoverWidget.background': '#001c40',
'editorHoverWidget.border': '#ffffff44',
'editorLineNumber.activeForeground': '#949494',
'editorWhitespace.foreground': '#404f7d',
'editorWidget.background': '#001c40',
errorForeground: '#a92049',
focusBorder: '#bbdaff',
'input.background': '#001733',
'list.activeSelectionBackground': '#ffffff60',
'list.highlightForeground': '#bbdaff',
'list.hoverBackground': '#ffffff30',
'list.inactiveSelectionBackground': '#ffffff40',
'minimap.selectionHighlight': '#003f8e',
'peekViewResult.background': '#001c40',
'pickerGroup.foreground': '#bbdaff',
'ports.iconRunningProcessForeground': '#bbdaff',
'progressBar.background': '#bbdaffcc',
'quickInputList.focusBackground': '#ffffff60',
'sideBar.background': '#001c40',
'statusBar.background': '#001126',
'statusBar.debuggingBackground': '#001126',
'statusBar.noFolderBackground': '#001126',
'statusBarItem.remoteBackground': '#0e639c',
'tab.inactiveBackground': '#001c40',
'tab.lastPinnedBorder': '#007acc80',
'terminal.ansiBlack': '#111111',
'terminal.ansiBlue': '#bbdaff',
'terminal.ansiBrightBlack': '#333333',
'terminal.ansiBrightBlue': '#80baff',
'terminal.ansiBrightCyan': '#78ffff',
'terminal.ansiBrightGreen': '#b8f171',
'terminal.ansiBrightMagenta': '#d778ff',
'terminal.ansiBrightRed': '#ff7882',
'terminal.ansiBrightWhite': '#ffffff',
'terminal.ansiBrightYellow': '#ffe580',
'terminal.ansiCyan': '#99ffff',
'terminal.ansiGreen': '#d1f1a9',
'terminal.ansiMagenta': '#ebbbff',
'terminal.ansiRed': '#ff9da4',
'terminal.ansiWhite': '#cccccc',
'terminal.ansiYellow': '#ffeead',
'titleBar.activeBackground': '#001126',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'meta.jsx.children',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'comment',
settings: {
foreground: '#7285B7',
},
},
{
scope: 'keyword.operator.class, keyword.operator, constant.other, source.php.embedded.line',
settings: {
foreground: '#FFFFFF',
fontStyle: '',
},
},
{
scope: 'variable, support.other.variable, string.other.link, string.regexp, entity.name.tag, entity.other.attribute-name, meta.tag, declaration.tag, markup.deleted.git_gutter',
settings: {
foreground: '#FF9DA4',
},
},
{
scope: 'constant.numeric, constant.language, support.constant, constant.character, variable.parameter, punctuation.section.embedded, keyword.other.unit',
settings: {
foreground: '#FFC58F',
fontStyle: '',
},
},
{
scope: 'entity.name.class, entity.name.type, entity.name.namespace, entity.name.scope-resolution, support.type, support.class',
settings: {
foreground: '#FFEEAD',
fontStyle: '',
},
},
{
scope: 'string, constant.other.symbol, entity.other.inherited-class, markup.heading, markup.inserted.git_gutter',
settings: {
foreground: '#D1F1A9',
fontStyle: '',
},
},
{
scope: 'keyword.operator, constant.other.color',
settings: {
foreground: '#99FFFF',
},
},
{
scope: 'entity.name.function, meta.function-call, support.function, keyword.other.special-method, meta.block-level, markup.changed.git_gutter',
settings: {
foreground: '#BBDAFF',
fontStyle: '',
},
},
{
scope: 'keyword, storage, storage.type, entity.name.tag.css',
settings: {
foreground: '#EBBBFF',
fontStyle: '',
},
},
{
scope: 'invalid',
settings: {
foreground: '#A92049',
fontStyle: '',
},
},
{
scope: 'meta.separator',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'invalid.deprecated',
settings: {
foreground: '#CD9731',
fontStyle: '',
},
},
{
scope: 'markup.inserted.diff, markup.deleted.diff, meta.diff.header.to-file, meta.diff.header.from-file',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'markup.inserted.diff, meta.diff.header.to-file',
settings: {
foreground: '#718C00',
},
},
{
scope: 'markup.deleted.diff, meta.diff.header.from-file',
settings: {
foreground: '#C82829',
},
},
{
scope: 'meta.diff.header.from-file, meta.diff.header.to-file',
settings: {
foreground: '#4271AE',
},
},
{
scope: 'meta.diff.range',
settings: {
foreground: '#3E999F',
fontStyle: 'italic',
},
},
{
scope: 'markup.quote',
settings: {
foreground: '#FFC58F',
},
},
{
scope: 'markup.list',
settings: {
foreground: '#BBDAFF',
},
},
{
scope: 'markup.bold, markup.italic',
settings: {
foreground: '#FFC58F',
},
},
{
scope: 'markup.bold',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#FF9DA4',
fontStyle: '',
},
},
{
scope: 'markup.heading',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,412 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const vsDark: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'dark',
colors: {
'actionBar.toggledBackground': '#383a49',
'activityBarBadge.background': '#007acc',
'checkbox.border': '#6b6b6b',
'editor.background': '#1e1e1e',
'editor.foreground': '#d4d4d4',
'editor.inactiveSelectionBackground': '#3a3d41',
'editor.selectionHighlightBackground': '#add6ff26',
'editorIndentGuide.activeBackground1': '#707070',
'editorIndentGuide.background1': '#404040',
'input.placeholderForeground': '#a6a6a6',
'list.activeSelectionIconForeground': '#ffffff',
'list.dropBackground': '#383b3d',
'menu.background': '#252526',
'menu.border': '#454545',
'menu.foreground': '#cccccc',
'menu.separatorBackground': '#454545',
'ports.iconRunningProcessForeground': '#369432',
'sideBarSectionHeader.background': '#00000000',
'sideBarSectionHeader.border': '#cccccc33',
'sideBarTitle.foreground': '#bbbbbb',
'statusBarItem.remoteBackground': '#16825d',
'statusBarItem.remoteForeground': '#ffffff',
'tab.lastPinnedBorder': '#cccccc33',
'terminal.inactiveSelectionBackground': '#3a3d41',
'widget.border': '#303031',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'header',
settings: {
foreground: '#000080',
},
},
{
scope: 'comment',
settings: {
foreground: '#6A9955',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#569CD6',
},
},
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
],
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#646695',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'entity.name.tag.css',
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#D7BA7D',
},
},
{
scope: 'invalid',
settings: {
foreground: '#F44747',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
foreground: '#569CD6',
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#569CD6',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'punctuation.definition.quote.begin.markdown',
settings: {
foreground: '#6A9955',
},
},
{
scope: 'punctuation.definition.list.begin.markdown',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'punctuation.definition.tag',
settings: {
foreground: '#808080',
},
},
{
scope: ['meta.preprocessor', 'entity.name.function.preprocessor'],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#569CD6',
},
},
{
scope: ['storage.modifier', 'keyword.operator.noexcept'],
settings: {
foreground: '#569CD6',
},
},
{
scope: ['string', 'meta.embedded.assembly'],
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.tag',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.value',
settings: {
foreground: '#CE9178',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#D16969',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'keyword',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#D4D4D4',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'],
settings: {
foreground: '#569CD6',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#9CDCFE',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#B5CEA8',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#D4D4D4',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#569CD6',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#FFFFFF',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#6796E6',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#F44747',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#B267E6',
},
},
],
};
@@ -1,435 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ThemeRegistrationAny } from 'shiki';
export const vsLight: ThemeRegistrationAny = {
$schema: 'vscode://schemas/color-theme',
type: 'light',
colors: {
'actionBar.toggledBackground': '#dddddd',
'activityBarBadge.background': '#007acc',
'checkbox.border': '#919191',
'editor.background': '#ffffff',
'editor.foreground': '#000000',
'editor.inactiveSelectionBackground': '#e5ebf1',
'editor.selectionHighlightBackground': '#add6ff80',
'editorIndentGuide.activeBackground1': '#939393',
'editorIndentGuide.background1': '#d3d3d3',
'editorSuggestWidget.background': '#f3f3f3',
'input.placeholderForeground': '#767676',
'list.activeSelectionIconForeground': '#ffffff',
'list.focusAndSelectionOutline': '#90c2f9',
'list.hoverBackground': '#e8e8e8',
'menu.border': '#d4d4d4',
'notebook.cellBorderColor': '#e8e8e8',
'notebook.selectedCellBackground': '#c8ddf150',
'ports.iconRunningProcessForeground': '#369432',
'searchEditor.textInputBorder': '#cecece',
'settings.numberInputBorder': '#cecece',
'settings.textInputBorder': '#cecece',
'sideBarSectionHeader.background': '#00000000',
'sideBarSectionHeader.border': '#61616130',
'sideBarTitle.foreground': '#6f6f6f',
'statusBarItem.errorBackground': '#c72e0f',
'statusBarItem.remoteBackground': '#16825d',
'statusBarItem.remoteForeground': '#ffffff',
'tab.lastPinnedBorder': '#61616130',
'terminal.inactiveSelectionBackground': '#e5ebf1',
'widget.border': '#d4d4d4',
},
tokenColors: [
{
scope: [
'meta.embedded',
'source.groovy.embedded',
'string meta.image.inline.markdown',
'variable.legacy.builtin.python',
],
settings: {
foreground: '#000000',
},
},
{
scope: 'emphasis',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'strong',
settings: {
fontStyle: 'bold',
},
},
{
scope: 'meta.diff.header',
settings: {
foreground: '#000080',
},
},
{
scope: 'comment',
settings: {
foreground: '#008000',
},
},
{
scope: 'constant.language',
settings: {
foreground: '#0000FF',
},
},
{
scope: [
'constant.numeric',
'variable.other.enummember',
'keyword.operator.plus.exponent',
'keyword.operator.minus.exponent',
],
settings: {
foreground: '#098658',
},
},
{
scope: 'constant.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: 'entity.name.tag',
settings: {
foreground: '#800000',
},
},
{
scope: 'entity.name.selector',
settings: {
foreground: '#800000',
},
},
{
scope: 'entity.other.attribute-name',
settings: {
foreground: '#E50000',
},
},
{
scope: [
'entity.other.attribute-name.class.css',
'entity.other.attribute-name.class.mixin.css',
'entity.other.attribute-name.id.css',
'entity.other.attribute-name.parent-selector.css',
'entity.other.attribute-name.pseudo-class.css',
'entity.other.attribute-name.pseudo-element.css',
'source.css.less entity.other.attribute-name.id',
'entity.other.attribute-name.scss',
],
settings: {
foreground: '#800000',
},
},
{
scope: 'invalid',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'markup.underline',
settings: {
fontStyle: 'underline',
},
},
{
scope: 'markup.bold',
settings: {
foreground: '#000080',
fontStyle: 'bold',
},
},
{
scope: 'markup.heading',
settings: {
foreground: '#800000',
fontStyle: 'bold',
},
},
{
scope: 'markup.italic',
settings: {
fontStyle: 'italic',
},
},
{
scope: 'markup.strikethrough',
settings: {
fontStyle: 'strikethrough',
},
},
{
scope: 'markup.inserted',
settings: {
foreground: '#098658',
},
},
{
scope: 'markup.deleted',
settings: {
foreground: '#A31515',
},
},
{
scope: 'markup.changed',
settings: {
foreground: '#0451A5',
},
},
{
scope: ['punctuation.definition.quote.begin.markdown', 'punctuation.definition.list.begin.markdown'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'markup.inline.raw',
settings: {
foreground: '#800000',
},
},
{
scope: 'punctuation.definition.tag',
settings: {
foreground: '#800000',
},
},
{
scope: ['meta.preprocessor', 'entity.name.function.preprocessor'],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'meta.preprocessor.string',
settings: {
foreground: '#A31515',
},
},
{
scope: 'meta.preprocessor.numeric',
settings: {
foreground: '#098658',
},
},
{
scope: 'meta.structure.dictionary.key.python',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'storage',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'storage.type',
settings: {
foreground: '#0000FF',
},
},
{
scope: ['storage.modifier', 'keyword.operator.noexcept'],
settings: {
foreground: '#0000FF',
},
},
{
scope: ['string', 'meta.embedded.assembly'],
settings: {
foreground: '#A31515',
},
},
{
scope: [
'string.comment.buffered.block.pug',
'string.quoted.pug',
'string.interpolated.pug',
'string.unquoted.plain.in.yaml',
'string.unquoted.plain.out.yaml',
'string.unquoted.block.yaml',
'string.quoted.single.yaml',
'string.quoted.double.xml',
'string.quoted.single.xml',
'string.unquoted.cdata.xml',
'string.quoted.double.html',
'string.quoted.single.html',
'string.unquoted.html',
'string.quoted.single.handlebars',
'string.quoted.double.handlebars',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'string.regexp',
settings: {
foreground: '#811F3F',
},
},
{
scope: [
'punctuation.definition.template-expression.begin',
'punctuation.definition.template-expression.end',
'punctuation.section.embedded',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: ['meta.template.expression'],
settings: {
foreground: '#000000',
},
},
{
scope: [
'support.constant.property-value',
'support.constant.font-name',
'support.constant.media-type',
'support.constant.media',
'constant.other.color.rgb-value',
'constant.other.rgb-value',
'support.constant.color',
],
settings: {
foreground: '#0451A5',
},
},
{
scope: [
'support.type.vendored.property-name',
'support.type.property-name',
'variable.css',
'variable.scss',
'variable.other.less',
'source.coffee.embedded',
],
settings: {
foreground: '#E50000',
},
},
{
scope: ['support.type.property-name.json'],
settings: {
foreground: '#0451A5',
},
},
{
scope: 'keyword',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.control',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.operator',
settings: {
foreground: '#000000',
},
},
{
scope: [
'keyword.operator.new',
'keyword.operator.expression',
'keyword.operator.cast',
'keyword.operator.sizeof',
'keyword.operator.alignof',
'keyword.operator.typeid',
'keyword.operator.alignas',
'keyword.operator.instanceof',
'keyword.operator.logical.python',
'keyword.operator.wordlike',
],
settings: {
foreground: '#0000FF',
},
},
{
scope: 'keyword.other.unit',
settings: {
foreground: '#098658',
},
},
{
scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'],
settings: {
foreground: '#800000',
},
},
{
scope: 'support.function.git-rebase',
settings: {
foreground: '#0451A5',
},
},
{
scope: 'constant.sha.git-rebase',
settings: {
foreground: '#098658',
},
},
{
scope: ['storage.modifier.import.java', 'variable.language.wildcard.java', 'storage.modifier.package.java'],
settings: {
foreground: '#000000',
},
},
{
scope: 'variable.language',
settings: {
foreground: '#0000FF',
},
},
{
scope: 'ref.matchtext',
settings: {
foreground: '#000000',
},
},
{
scope: 'token.info-token',
settings: {
foreground: '#316BCD',
},
},
{
scope: 'token.warn-token',
settings: {
foreground: '#CD9731',
},
},
{
scope: 'token.error-token',
settings: {
foreground: '#CD3131',
},
},
{
scope: 'token.debug-token',
settings: {
foreground: '#800080',
},
},
],
};
@@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function getNonce() {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
export function pluralize(count: number, noun: string, suffix = 's') {
return `${count} ${noun}${count !== 1 ? suffix : ''}`;
}
@@ -1,134 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, Disposable, languages, LanguageStatusItem, LanguageStatusSeverity, window, workspace } from 'vscode';
import { IDisposable } from '../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { CopilotConfigPrefix } from '../../lib/src/constants';
import { CMDQuotaExceeded } from '../../lib/src/openai/fetch';
import { StatusChangedEvent, StatusReporter } from '../../lib/src/progress';
import { isCompletionEnabled, isInlineSuggestEnabled } from './config';
import { CMDToggleStatusMenuChat } from './constants';
import { ICompletionsExtensionStatus } from './extensionStatus';
import { Icon } from './icon';
export class CopilotStatusBar extends StatusReporter implements IDisposable {
readonly item!: LanguageStatusItem;
showingMessage = false;
private disposables: Disposable[] = [];
constructor(
id: string,
@ICompletionsExtensionStatus readonly extensionStatusService: ICompletionsExtensionStatus,
@IInstantiationService readonly instantiationService: IInstantiationService,
) {
super();
this.item = languages.createLanguageStatusItem(id, '*');
this.disposables.push(this.item);
this.updateStatusBarIndicator();
this.disposables.push(
window.onDidChangeActiveTextEditor(() => {
this.updateStatusBarIndicator();
})
);
this.disposables.push(
workspace.onDidCloseTextDocument(() => {
this.updateStatusBarIndicator();
})
);
this.disposables.push(
workspace.onDidOpenTextDocument(() => {
this.updateStatusBarIndicator();
})
);
this.disposables.push(
workspace.onDidChangeConfiguration(e => {
if (!e.affectsConfiguration(CopilotConfigPrefix)) { return; }
this.updateStatusBarIndicator();
})
);
}
override didChange(event: StatusChangedEvent): void {
this.extensionStatusService.kind = event.kind;
this.extensionStatusService.message = event.message;
this.extensionStatusService.command = event.command;
this.updateStatusBarIndicator();
}
private checkEnabledForLanguage(): boolean {
return this.instantiationService.invokeFunction(isCompletionEnabled) ?? true;
}
protected updateStatusBarIndicator() {
if (this.isDisposed()) {
return;
}
void commands.executeCommand(
'setContext',
'github.copilot.completions.quotaExceeded',
this.extensionStatusService.command?.command === CMDQuotaExceeded
);
const enabled = this.checkEnabledForLanguage();
void commands.executeCommand('setContext', 'github.copilot.completions.enabled', enabled);
this.item.command = { command: CMDToggleStatusMenuChat, title: 'View Details' };
switch (this.extensionStatusService.kind) {
case 'Error':
this.item.severity = LanguageStatusSeverity.Error;
this.item.text = `${Icon.Warning} Completions`;
this.item.detail = 'Error';
break;
case 'Warning':
this.item.severity = LanguageStatusSeverity.Warning;
this.item.text = `${Icon.Warning} Completions`;
this.item.detail = 'Temporary issues';
break;
case 'Inactive':
this.item.severity = LanguageStatusSeverity.Information;
this.item.text = `${Icon.Blocked} Completions`;
this.item.detail = 'Inactive';
break;
case 'Normal':
this.item.severity = LanguageStatusSeverity.Information;
if (!isInlineSuggestEnabled()) {
this.item.text = `${Icon.NotConnected} Completions`;
this.item.detail = 'VS Code inline suggestions disabled';
} else if (!enabled) {
this.item.text = `${Icon.NotConnected} Completions`;
this.item.detail = 'Disabled';
} else {
this.item.text = `${Icon.Logo} Completions`;
this.item.detail = '';
}
this.item.command.title = 'Open Menu';
break;
}
this.item.accessibilityInformation = {
label: 'Inline Suggestions',
};
if (this.extensionStatusService.command) {
this.item.command = this.extensionStatusService.command;
this.item.detail = this.extensionStatusService.message;
}
}
dispose() {
for (const d of this.disposables) {
d.dispose();
}
this.disposables = [];
}
private isDisposed() {
return this.disposables.length === 0;
}
}
@@ -1,169 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QuickPick, QuickPickItem, QuickPickItemKind, commands, l10n, window } from 'vscode';
import { isWeb } from '../../../../../util/vs/base/common/platform';
import { IInstantiationService } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { isCompletionEnabled, isInlineSuggestEnabled } from './config';
import { CMDCollectDiagnosticsChat, CMDDisableCompletionsChat, CMDEnableCompletionsChat, CMDOpenDocumentationClient, CMDOpenLogsClient, CMDOpenModelPickerClient, CMDOpenPanelClient } from './constants';
import { ICompletionsExtensionStatus } from './extensionStatus';
import { Icon } from './icon';
export class CopilotStatusBarPickMenu {
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICompletionsExtensionStatus private readonly extensionStatusService: ICompletionsExtensionStatus,
) { }
showStatusMenu() {
const quickpickList = window.createQuickPick();
quickpickList.placeholder = l10n.t('Select an option');
quickpickList.title = l10n.t('Configure Inline Suggestions');
quickpickList.items = this.collectQuickPickItems();
quickpickList.onDidAccept(() => this.handleItemSelection(quickpickList));
quickpickList.show();
return quickpickList;
}
async handleItemSelection(quickpickList: QuickPick<QuickPickItem>): Promise<void> {
const selection = quickpickList.selectedItems[0];
if (selection === undefined) { return; }
if ('command' in selection) {
const commandSelection = selection as CommandQuickItem;
await commands.executeCommand(commandSelection.command, ...commandSelection.commandArgs);
quickpickList.hide();
} else {
throw new Error('Unexpected Copilot quick picker selection');
}
}
private collectQuickPickItems() {
return [
this.newStatusItem(),
this.newSeparator(),
...this.collectLanguageSpecificItems(),
this.newKeyboardItem(),
this.newSettingsItem(),
...this.collectDiagnosticsItems(),
this.newOpenLogsItem(),
this.newSeparator(),
this.newDocsItem(),
//this.newForumItem(),
];
}
private collectLanguageSpecificItems() {
const items: QuickPickItem[] = [];
if (!this.hasActiveStatus()) { return items; }
const editor = window.activeTextEditor;
if (!isWeb && editor) { items.push(this.newPanelItem()); }
// Always show the model picker even if only one model is available
if (!isWeb) { items.push(this.newChangeModelItem()); }
if (editor) { items.push(...this.newEnableLanguageItem()); }
if (items.length) { items.push(this.newSeparator()); }
return items;
}
private hasActiveStatus() {
return ['Normal'].includes(this.extensionStatusService.kind);
}
private isCompletionEnabled() {
return isInlineSuggestEnabled() && this.instantiationService.invokeFunction(isCompletionEnabled);
}
private newEnableLanguageItem() {
const isEnabled = this.isCompletionEnabled();
if (isEnabled) {
return [this.newCommandItem(l10n.t('Disable Inline Suggestions'), CMDDisableCompletionsChat)];
} else if (isEnabled === false) {
return [this.newCommandItem(l10n.t('Enable Inline Suggestions'), CMDEnableCompletionsChat)];
} else {
return [];
}
}
private newStatusItem() {
let statusText;
let statusIcon = Icon.Logo;
switch (this.extensionStatusService.kind) {
case 'Normal':
statusText = l10n.t('Ready');
if (isInlineSuggestEnabled() === false) {
statusText += ` (${l10n.t('VS Code inline suggestions disabled')})`;
} else if (this.instantiationService.invokeFunction(isCompletionEnabled) === false) {
statusText += ` (${l10n.t('Disabled')})`;
}
break;
case 'Inactive':
statusText = this.extensionStatusService.message || l10n.t('Copilot is currently inactive');
statusIcon = Icon.Blocked;
break;
default:
statusText = this.extensionStatusService.message || l10n.t('Copilot has encountered an error');
statusIcon = Icon.NotConnected;
break;
}
return this.newCommandItem(`${statusIcon} ${l10n.t('Status')}: ${statusText}`, CMDOpenLogsClient);
}
private newOpenLogsItem() {
return this.newCommandItem(l10n.t('Open Logs...'), CMDOpenLogsClient);
}
private collectDiagnosticsItems() {
if (isWeb) { return []; }
return [this.newCommandItem(l10n.t('Show Diagnostics...'), CMDCollectDiagnosticsChat)];
}
private newKeyboardItem() {
return this.newCommandItem(l10n.t('$(keyboard) Edit Keyboard Shortcuts...'), 'workbench.action.openGlobalKeybindings', [
'copilot',
]);
}
private newSettingsItem() {
return this.newCommandItem(l10n.t('$(settings-gear) Edit Settings...'), 'workbench.action.openSettings', [
'GitHub Copilot',
]);
}
private newPanelItem() {
return this.newCommandItem(l10n.t('Open Completions Panel...'), CMDOpenPanelClient);
}
private newChangeModelItem() {
return this.newCommandItem(l10n.t('Change Completions Model...'), CMDOpenModelPickerClient);
}
private newDocsItem() {
return this.newCommandItem(
l10n.t('$(remote-explorer-documentation) View Copilot Documentation...'),
CMDOpenDocumentationClient
);
}
private newCommandItem(label: string, command: string, commandArgs?: string[]): CommandQuickItem {
return new CommandQuickItem(label, command, commandArgs || []);
}
private newSeparator(): QuickPickItem {
return {
label: '',
kind: QuickPickItemKind.Separator,
};
}
}
class CommandQuickItem implements QuickPickItem {
constructor(
readonly label: string,
readonly command: string,
readonly commandArgs: string[]
) { }
}
@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, Disposable } from 'vscode';
import { IDisposable } from '../../../../../util/vs/base/common/lifecycle';
import { IInstantiationService, type ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { handleException } from '../../lib/src/defaultHandlers';
import { Logger } from '../../lib/src/logger';
function exception(accessor: ServicesAccessor, error: unknown, origin: string, logger?: Logger) {
if (error instanceof Error && error.name === 'Canceled') {
// these are VS Code cancellations
return;
}
if (error instanceof Error && error.name === 'CodeExpectedError') {
// expected errors from VS Code
return;
}
handleException(accessor, error, origin, logger);
}
export function registerCommand(accessor: ServicesAccessor, command: string, fn: (...args: unknown[]) => unknown): Disposable {
const instantiationService = accessor.get(IInstantiationService);
try {
const disposable = commands.registerCommand(command, async (...args: unknown[]) => {
try {
await fn(...args);
} catch (error) {
// Pass in the command string as the origin
instantiationService.invokeFunction(exception, error, command);
}
});
return disposable;
} catch (error) {
console.error(`Error registering command ${command}:`, error);
throw error;
}
}
// Wrapper that handles errors and cleans up the command on extension deactivation
export function registerCommandWrapper(accessor: ServicesAccessor, command: string, fn: (...args: unknown[]) => unknown): IDisposable {
return registerCommand(accessor, command, fn);
}
@@ -1,25 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ConfigKey, ConfigKeyType, DefaultsOnlyConfigProvider, InMemoryConfigProvider } from '../../../lib/src/config';
import { VSCodeConfigProvider } from '../config';
/**
* Provides the default configurations, except lets through the configured value
* of test-only settings like the proxy override URL.
*/
export class ExtensionTestConfigProvider extends InMemoryConfigProvider {
private readonly vscConfigProvider = new VSCodeConfigProvider();
constructor() {
super(new DefaultsOnlyConfigProvider());
}
override getConfig<T>(key: ConfigKeyType): T {
if (key === ConfigKey.DebugTestOverrideProxyUrl) {
return this.vscConfigProvider.getConfig<T>(key);
}
return super.getConfig(key);
}
}
@@ -1,34 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncDescriptor } from '../../../../../../util/vs/platform/instantiation/common/descriptors';
import { createExtensionTestingServices } from '../../../../../test/vscode-node/services';
import { ICompletionsEditorAndPluginInfo } from '../../../lib/src/config';
import { ICompletionsFileSystemService } from '../../../lib/src/fileSystem';
import { ICompletionsFetcherService } from '../../../lib/src/networking';
import { _createBaselineContext } from '../../../lib/src/test/context';
import { StaticFetcher } from '../../../lib/src/test/fetcher';
import { ICompletionsTextDocumentManagerService } from '../../../lib/src/textDocumentManager';
import { VSCodeEditorInfo } from '../config';
import { CopilotExtensionStatus, ICompletionsExtensionStatus } from '../extensionStatus';
import { extensionFileSystem } from '../fileSystem';
import { ExtensionTextDocumentManager } from '../textDocumentManager';
import { ExtensionTestConfigProvider } from './config';
/**
* A default context for VSCode extension testing, building on general one in `lib`.
* Only includes items that are needed for almost all extension tests.
*/
export function createExtensionTestingContext() {
let serviceCollection = createExtensionTestingServices();
serviceCollection = _createBaselineContext(serviceCollection, new ExtensionTestConfigProvider());
serviceCollection.define(ICompletionsFetcherService, new StaticFetcher());
serviceCollection.define(ICompletionsEditorAndPluginInfo, new VSCodeEditorInfo());
serviceCollection.define(ICompletionsTextDocumentManagerService, new SyncDescriptor(ExtensionTextDocumentManager));
serviceCollection.define(ICompletionsFileSystemService, extensionFileSystem);
serviceCollection.define(ICompletionsExtensionStatus, new CopilotExtensionStatus());
return serviceCollection;
}
@@ -1,113 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
import sinon from 'sinon';
import { commands, env } from 'vscode';
import { SyncDescriptor } from '../../../../../../util/vs/platform/instantiation/common/descriptors';
import { IInstantiationService, ServicesAccessor } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { AvailableModelsManager, ICompletionsModelManagerService } from '../../../lib/src/openai/model';
import { ModelPickerManager } from './../modelPicker';
import { createExtensionTestingContext } from './context';
suite('ModelPickerManager unit tests', function () {
let accessor: ServicesAccessor;
let modelPicker: ModelPickerManager;
let availableModelsManager: ICompletionsModelManagerService;
let sandbox: sinon.SinonSandbox;
// Couple of fake models to use in our tests.
const fakeModels = [
{
modelId: 'model-a',
label: 'Model A',
type: 'model',
alwaysShow: true,
preview: false,
tokenizer: 'o200k_base',
},
{
modelId: 'model-b',
label: 'Model B',
type: 'model',
alwaysShow: true,
preview: false,
tokenizer: 'cl100k_base',
},
];
setup(function () {
sandbox = sinon.createSandbox();
// Create our test context, and stub the AvailableModelsManager to return our fake models.
const serviceCollection = createExtensionTestingContext();
serviceCollection.define(ICompletionsModelManagerService, new SyncDescriptor(AvailableModelsManager, [true]));
accessor = serviceCollection.createTestingAccessor();
availableModelsManager = accessor.get(ICompletionsModelManagerService);
sandbox.stub(availableModelsManager, 'getGenericCompletionModels').returns(fakeModels);
modelPicker = accessor.get(IInstantiationService).createInstance(ModelPickerManager);
});
teardown(async function () {
// Make sure to close any open quick pick dialogs after each test.
await commands.executeCommand('workbench.action.closeQuickOpen');
sandbox.restore();
});
test('showModelPicker returns correct items', function () {
const instantiationService = accessor.get(IInstantiationService);
modelPicker = instantiationService.createInstance(ModelPickerManager);
const quickPick = modelPicker.showModelPicker();
// Check that we have the correct number of items
// The items should include the two fake models, a separator, and a learn more item.
assert(quickPick.items.length === 4, quickPick.items.length.toString());
assert.strictEqual(quickPick.items[0].modelId, 'model-a');
assert.strictEqual(quickPick.items[1].modelId, 'model-b');
assert.strictEqual(quickPick.items[2].type, 'separator');
assert.strictEqual(quickPick.items[3].type, 'learn-more');
});
test('selecting a model updates user selection', async function () {
// Stub out setting model
const setModelStub = sandbox.stub(modelPicker, 'setUserSelectedCompletionModel').resolves();
const quickPick = modelPicker.showModelPicker();
const secondItem = quickPick.items[1];
assert(secondItem !== undefined, 'model picker should have a model-b second item.');
// Fake selecting the second item
quickPick.activeItems = [secondItem];
await modelPicker.handleModelSelection(quickPick);
// Test that we updated the user configuration with the selected model
assert(setModelStub.calledOnce, 'setUserSelectedCompletionModel should be called once');
assert.strictEqual(setModelStub.firstCall.args[0], secondItem.modelId);
});
test('selecting the learn more link tries to open the learn more url', async function () {
// Stub openExternal
const openUrlStub = sandbox.stub(env, 'openExternal').resolves();
const quickPick = modelPicker.showModelPicker();
const learnMoreItem = quickPick.items[3];
assert(learnMoreItem !== undefined, 'model picker should have a learn more item.');
// Fake selecting the learn more item
quickPick.activeItems = [learnMoreItem];
await modelPicker.handleModelSelection(quickPick);
// Test that we opened the learn more URL
assert(openUrlStub.calledOnce, 'openUrl should be called once');
assert.strictEqual(
openUrlStub.firstCall.args[0].toString(),
'https://aka.ms/CopilotCompletionsModelPickerLearnMore'
);
});
});
@@ -1,123 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { window, workspace } from 'vscode';
import { detectLanguage } from '../../lib/src/language/languageDetection';
import { CopilotTextDocument, INotebookCell, INotebookDocument, ITextDocument } from '../../lib/src/textDocument';
import { TextDocumentManager, WorkspaceFoldersChangeEvent } from '../../lib/src/textDocumentManager';
import { transformEvent } from '../../lib/src/util/event';
import { normalizeUri } from '../../lib/src/util/uri';
// List of document URI schemes that avoid ghost text suggestions
const ignoreUriSchemes = new Set([
'output', // vscode output pane (important: avoids infinite log loop)
'search-editor', // search results virtual document
'comment', // very little context available and suggestions are often bad
'git', // virtual file tracked by git
'chat-editing-snapshot-text-model', // VS Code Chat temporary editing snapshot
]);
export function wrapDoc(doc: vscode.TextDocument): ITextDocument | undefined {
if (ignoreUriSchemes.has(doc.uri.scheme)) {
return;
}
let text: string;
try {
text = doc.getText();
} catch (e) {
// "Invalid string length", it's too big to fit in a string
if (e instanceof RangeError) {
return;
}
throw e;
}
const languageId = detectLanguage({ uri: doc.uri.toString(), languageId: doc.languageId });
return CopilotTextDocument.create(doc.uri.toString(), doc.languageId, doc.version, text, languageId);
}
export class ExtensionTextDocumentManager extends TextDocumentManager {
override onDidFocusTextDocument = transformEvent(window.onDidChangeActiveTextEditor, event => {
return { document: event && { uri: event.document.uri.toString() } };
});
override onDidChangeTextDocument = transformEvent(workspace.onDidChangeTextDocument, e => {
const document = wrapDoc(e.document);
return document && { document, contentChanges: e.contentChanges };
});
override onDidOpenTextDocument = transformEvent(workspace.onDidOpenTextDocument, e => {
// use wrapDoc() to handle the "Invalid string length" case
const text = wrapDoc(e)?.getText();
if (text === undefined) {
return;
}
return { document: { uri: e.uri.toString(), languageId: e.languageId, version: e.version, text } };
});
override onDidCloseTextDocument = transformEvent(workspace.onDidCloseTextDocument, e => {
return { document: { uri: normalizeUri(e.uri.toString()) } };
});
override onDidChangeWorkspaceFolders = transformEvent(
workspace.onDidChangeWorkspaceFolders,
(e): WorkspaceFoldersChangeEvent => {
return {
workspaceFolders: this.getWorkspaceFolders(),
added: e.added.map(f => ({ uri: f.uri.toString(), name: f.name })),
removed: e.removed.map(f => ({ uri: f.uri.toString(), name: f.name })),
};
}
);
getTextDocumentsUnsafe(): ITextDocument[] {
const docs: ITextDocument[] = [];
for (const vscodeDoc of workspace.textDocuments) {
const doc = wrapDoc(vscodeDoc);
if (doc) {
docs.push(doc);
}
}
return docs;
}
findNotebook(doc: { uri: string }): INotebookDocument | undefined {
for (const notebook of workspace.notebookDocuments) {
if (notebook.getCells().some(cell => cell.document.uri.toString() === doc.uri.toString())) {
return {
getCells: () => notebook.getCells().map(cell => this.wrapCell(cell)),
getCellFor: ({ uri }: { uri: string }) => {
const cell = notebook.getCells().find(cell => cell.document.uri.toString() === uri.toString());
return cell ? this.wrapCell(cell) : undefined;
},
};
}
}
}
wrapCell(cell: vscode.NotebookCell): INotebookCell {
return {
...cell,
get document(): ITextDocument {
return CopilotTextDocument.create(
cell.document.uri.toString(),
cell.document.languageId,
cell.document.version,
cell.document.getText(),
// use the original language id as cells have no metadata to leverage for language detection
cell.document.languageId
);
},
};
}
getWorkspaceFolders() {
return (
workspace.workspaceFolders?.map(f => {
return { uri: f.uri.toString(), name: f.name };
}) ?? []
);
}
}
@@ -1,210 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
CancellationToken,
InlineCompletionContext,
InlineCompletionEndOfLifeReason,
InlineCompletionItemProvider,
InlineCompletionList,
InlineCompletionTriggerKind,
PartialAcceptInfo,
Position,
TextDocument,
workspace
} from 'vscode';
import { Disposable } from '../../../../../util/vs/base/common/lifecycle';
import { LineEdit } from '../../../../../util/vs/editor/common/core/edits/lineEdit';
import { TextEdit, TextReplacement } from '../../../../../util/vs/editor/common/core/edits/textEdit';
import { Range } from '../../../../../util/vs/editor/common/core/range';
import { LineBasedText } from '../../../../../util/vs/editor/common/core/text/abstractText';
import { IInstantiationService, ServicesAccessor } from '../../../../../util/vs/platform/instantiation/common/instantiation';
import { InlineEditLogger } from '../../../../inlineEdits/vscode-node/parts/inlineEditLogger';
import { GhostTextContext } from '../../../common/ghostTextContext';
import { ICompletionsTelemetryService } from '../../bridge/src/completionsTelemetryServiceBridge';
import { BuildInfo } from '../../lib/src/config';
import { CopilotConfigPrefix } from '../../lib/src/constants';
import { handleException } from '../../lib/src/defaultHandlers';
import { Logger } from '../../lib/src/logger';
import { isCompletionEnabledForDocument } from './config';
import { CopilotCompletionFeedbackTracker, sendCompletionFeedbackCommand } from './copilotCompletionFeedbackTracker';
import { ICompletionsExtensionStatus } from './extensionStatus';
import { GhostTextCompletionItem, GhostTextCompletionList, GhostTextProvider } from './ghostText/ghostTextProvider';
const logger = new Logger('inlineCompletionItemProvider');
function quickSuggestionsDisabled() {
const qs = workspace.getConfiguration('editor.quickSuggestions');
return qs.get('other') !== 'on' && qs.get('comments') !== 'on' && qs.get('strings') !== 'on';
}
export function exception(accessor: ServicesAccessor, error: unknown, origin: string, logger?: Logger) {
if (error instanceof Error && error.name === 'Canceled') {
// these are VS Code cancellations
return;
}
if (error instanceof Error && error.name === 'CodeExpectedError') {
// expected errors from VS Code
return;
}
const telemetryService = accessor.get(ICompletionsTelemetryService);
telemetryService.sendGHTelemetryException(error, 'codeUnification.completions.exception');
handleException(accessor, error, origin, logger);
}
/** @public */
export class CopilotInlineCompletionItemProvider extends Disposable implements InlineCompletionItemProvider {
private readonly copilotCompletionFeedbackTracker: CopilotCompletionFeedbackTracker;
private readonly ghostTextProvider: GhostTextProvider;
private readonly inlineEditLogger: InlineEditLogger;
public onDidChange = undefined;
public handleListEndOfLifetime: InlineCompletionItemProvider['handleListEndOfLifetime'] = undefined;
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICompletionsTelemetryService private readonly telemetryService: ICompletionsTelemetryService,
@ICompletionsExtensionStatus private readonly extensionStatusService: ICompletionsExtensionStatus,
) {
super();
this.copilotCompletionFeedbackTracker = this._register(this.instantiationService.createInstance(CopilotCompletionFeedbackTracker));
this.ghostTextProvider = this.instantiationService.createInstance(GhostTextProvider);
this.inlineEditLogger = this.instantiationService.createInstance(InlineEditLogger);
}
async provideInlineCompletionItems(
doc: TextDocument,
position: Position,
context: InlineCompletionContext,
token: CancellationToken
): Promise<GhostTextCompletionList | undefined> {
const logContext = new GhostTextContext(doc.uri.toString(), doc.version, context);
try {
return await this._provideInlineCompletionItems(doc, position, context, logContext, token);
} catch (e) {
logContext.setError(e);
this.telemetryService.sendGHTelemetryException(e, 'codeUnification.completions.exception');
} finally {
this.inlineEditLogger.add(logContext);
}
}
private async _provideInlineCompletionItems(
doc: TextDocument,
position: Position,
context: InlineCompletionContext,
logContext: GhostTextContext,
token: CancellationToken
): Promise<GhostTextCompletionList | undefined> {
if (context.triggerKind === InlineCompletionTriggerKind.Automatic) {
if (!this.instantiationService.invokeFunction(isCompletionEnabledForDocument, doc)) {
return;
}
if (this.extensionStatusService.kind === 'Error') {
return;
}
}
const copilotConfig = workspace.getConfiguration(CopilotConfigPrefix);
// Constraining the generated inline completion to match selectedCompletionInfo sandbags Copilot pretty hard, as
// typically it's just the first entry in the list alphabetically. But if we generate a result that doesn't
// match it, VS Code won't show it to the user unless the completion dropdown is dismissed. Historically we've
// chosen to favor completion quality, but this option allows opting into or out of generating a completion that
// VS Code will actually show.
if (!copilotConfig.get('respectSelectedCompletionInfo', quickSuggestionsDisabled() || BuildInfo.isPreRelease())) {
context = { ...context, selectedCompletionInfo: undefined };
}
try {
let items = await this.ghostTextProvider.provideInlineCompletionItems(doc, position, context, token);
if (!items) {
if (token.isCancellationRequested) {
logContext.setIsSkipped();
}
return undefined;
}
// If the language client provides a list of items, we want to add the send feedback command to it.
if (Array.isArray(items)) {
items = { items };
}
this.logSuggestion(logContext, doc, items);
return {
...items,
commands: [sendCompletionFeedbackCommand],
};
} catch (e) {
this.instantiationService.invokeFunction(exception, e, '._provideInlineCompletionItems', logger);
logContext.setError(e);
}
}
handleDidShowCompletionItem(item: GhostTextCompletionItem) {
try {
this.copilotCompletionFeedbackTracker.trackItem(item);
return this.ghostTextProvider.handleDidShowCompletionItem(item);
} catch (e) {
this.instantiationService.invokeFunction(exception, e, '.handleDidShowCompletionItem', logger);
}
}
handleDidPartiallyAcceptCompletionItem(
item: GhostTextCompletionItem,
acceptedLengthOrInfo: number | PartialAcceptInfo
) {
try {
return this.ghostTextProvider.handleDidPartiallyAcceptCompletionItem(item, acceptedLengthOrInfo);
} catch (e) {
this.instantiationService.invokeFunction(exception, e, '.handleDidPartiallyAcceptCompletionItem', logger);
}
}
handleEndOfLifetime(completionItem: GhostTextCompletionItem, reason: InlineCompletionEndOfLifeReason) {
try {
return this.ghostTextProvider.handleEndOfLifetime(completionItem, reason);
} catch (e) {
this.instantiationService.invokeFunction(exception, e, '.handleEndOfLifetime', logger);
}
}
private logSuggestion(
logContext: GhostTextContext,
doc: TextDocument,
items: InlineCompletionList
) {
if (items.items.length === 0) {
logContext.markAsNoSuggestions();
logContext.addLog('No inline completion items provided');
return;
}
const firstItem = items.items[0];
if (!firstItem.range) {
logContext.addLog('Inline completion item has no range');
return;
}
if (typeof firstItem.insertText !== 'string') {
logContext.addLog('Inline completion item has non-string insertText');
return;
}
const text = new LineBasedText(lineNumber => doc.lineAt(lineNumber - 1).text, doc.lineCount);
const lineEdit = LineEdit.fromTextEdit(
new TextEdit(
[new TextReplacement(
new Range(firstItem.range.start.line + 1, firstItem.range.start.character + 1, firstItem.range.end.line + 1, firstItem.range.end.character + 1),
firstItem.insertText,
)],
),
text
);
const patch = lineEdit.humanReadablePatch(text.getLines());
logContext.setResult(patch);
}
}
@@ -1,75 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
require('tsx/cjs');
const { globSync } = require('glob');
const Mocha = require('mocha');
const path = require('path');
const dotenv = require('dotenv');
const envfile = path.join(__dirname, '../../../../../../.env');
dotenv.config({ path: envfile });
function run() {
const projectRoot = path.resolve(__dirname, '../..');
const mochaOptions = {
ui: 'tdd',
color: !process.env.NO_COLOR && process.env.TERM !== 'dumb',
reporter: 'mocha-multi-reporters',
reporterOptions: {
reporterEnabled: 'spec',
},
};
if (process.env.MOCHA_GREP) {
mochaOptions.grep = process.env.MOCHA_GREP;
}
if (process.env.CI) {
mochaOptions.forbidOnly = true;
mochaOptions.retries = 2;
mochaOptions.reporterOptions.reporterEnabled += ', mocha-junit-reporter';
mochaOptions.reporterOptions.mochaJunitReporterReporterOptions = {
testCaseSwitchClassnameAndName: true,
testsuitesTitle: 'Copilot VS Code Extension Tests',
mochaFile: path.resolve(projectRoot, 'test-results-Extension.xml'),
};
}
if (process.env.GITHUB_EVENT_NAME === 'merge_group') {
mochaOptions.retries = 3;
}
// Create the mocha test
const mocha = new Mocha(mochaOptions);
let fileCount = 0;
(process.env.MOCHA_FILES || [
path.resolve(projectRoot, 'lib/src/**/*.test.{ts,tsx}'),
path.resolve(projectRoot, 'extension/src/**/*.test.{ts,tsx}')
].join('\n')).split('\n').forEach(f => {
globSync(f, { windowsPathsNoEscape: true }).forEach(f => {
fileCount++;
mocha.addFile(f);
});
});
if (!fileCount) {
throw new Error('No tests to run');
}
return new Promise((c, e) => {
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
e(err);
}
});
}
module.exports = { run };
@@ -1,79 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { runTests } from '@vscode/test-electron';
async function main() {
const tempdir = await fs.mkdtemp(os.tmpdir() + '/copilot-extension-test-');
let exitCode;
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../..');
// The path to the extension test script (must be javascript)
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './run');
const launchArgs = [];
// Disable other extensions while testing,
launchArgs.push('--disable-extensions');
// use a temporary folder so we can run multiple instances of the same VS Code together
// see https://github.com/microsoft/vscode/issues/137678
launchArgs.push('--user-data-dir', tempdir);
const argv = await yargs(hideBin(process.argv))
.options({
stable: {
type: 'boolean',
default: false,
},
grep: {
alias: 'g',
type: 'string',
default: '',
},
})
.parse();
const version = argv.stable ? 'stable' : 'insiders';
const extensionTestsEnv: typeof process.env = {};
// Pass arguments to mocha by environment variables
if (argv.grep) { extensionTestsEnv.MOCHA_GREP = argv.grep; }
if (argv._.length > 0) { extensionTestsEnv.MOCHA_FILES = argv._.join('\n'); }
if (!process.stdout.isTTY) { extensionTestsEnv.NO_COLOR = 'true'; }
const workspaceFolder = await fs.mkdtemp(path.join(os.tmpdir(), 'copilot-extension-test-'));
launchArgs.push(workspaceFolder);
extensionTestsEnv.CORETEST = 'true';
//@dbaeumer This can be removed as soon as we have the cache handle CORETEST
extensionTestsEnv.VITEST = 'true';
// Download VS Code, unzip it and run the integration test
exitCode = await runTests({
version,
extensionDevelopmentPath,
extensionTestsPath,
launchArgs,
extensionTestsEnv,
});
} catch (err) {
console.error('Failed to run tests', err);
exitCode = 1;
} finally {
await fs.rm(tempdir, { recursive: true });
}
process.exit(exitCode);
}
void main();
@@ -1,72 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
import { CopilotToken } from '../../../../../../platform/authentication/common/copilotToken';
import { createServiceIdentifier } from '../../../../../../util/common/services';
import { ThrottledDelayer } from '../../../../../../util/vs/base/common/async';
import { Disposable } from '../../../../../../util/vs/base/common/lifecycle';
export { CopilotToken } from '../../../../../../platform/authentication/common/copilotToken';
export const ICompletionsCopilotTokenManager = createServiceIdentifier<ICompletionsCopilotTokenManager>('ICompletionsCopilotTokenManager');
export interface ICompletionsCopilotTokenManager {
readonly _serviceBrand: undefined;
get token(): CopilotToken | undefined;
primeToken(): Promise<boolean>;
getToken(): Promise<CopilotToken>;
resetToken(httpError?: number): void;
getLastToken(): Omit<CopilotToken, 'token'> | undefined;
}
export class CopilotTokenManagerImpl extends Disposable implements ICompletionsCopilotTokenManager {
declare _serviceBrand: undefined;
private tokenRefetcher = new ThrottledDelayer(5_000);
private _token: CopilotToken | undefined;
get token() {
void this.tokenRefetcher.trigger(() => this.updateCachedToken());
return this._token;
}
constructor(
protected primed = false,
@IAuthenticationService private readonly authenticationService: IAuthenticationService
) {
super();
this.updateCachedToken();
this._register(this.authenticationService.onDidAuthenticationChange(() => this.updateCachedToken()));
}
/**
* Ensure we have a token and that the `StatusReporter` is up to date.
*/
primeToken(): Promise<boolean> {
try {
return this.getToken().then(
() => true,
() => false
);
} catch (e) {
return Promise.resolve(false);
}
}
async getToken(): Promise<CopilotToken> {
return this.updateCachedToken();
}
private async updateCachedToken(): Promise<CopilotToken> {
this._token = await this.authenticationService.getCopilotToken();
return this._token;
}
resetToken(httpError?: number): void {
this.authenticationService.resetCopilotToken();
}
getLastToken(): Omit<CopilotToken, 'token'> | undefined {
return this.authenticationService.copilotToken;
}
}
@@ -1,16 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAuthenticationService } from '../../../../../../platform/authentication/common/authentication';
import { CopilotToken } from '../../../../../../platform/authentication/common/copilotToken';
export function onCopilotToken(authService: IAuthenticationService, listener: (token: Omit<CopilotToken, 'token'>) => unknown) {
return authService.onDidAuthenticationChange(() => {
const copilotToken = authService.copilotToken;
if (copilotToken) {
listener(copilotToken);
}
});
}
@@ -1,27 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CopilotToken } from './copilotTokenManager';
/**
* A function used to determine if the org list contains an known organization
* @param orgs The list of organizations the user is a member of
* @returns The first known organization or undefined if none are known.
*/
function findKnownOrg(orgs: string[]): string | undefined {
// Do not add org mapping
const known_orgs = [
'a5db0bcaae94032fe715fb34a5e4bce2',
'7184f66dfcee98cb5f08a1cb936d5225',
'faef89d9169d5eacf1d8c8dde3412e37',
'4535c7beffc844b46bb1ed4aa04d759a',
];
return known_orgs.find(o => orgs.includes(o));
}
export function getUserKind(token: Omit<CopilotToken, 'token'>): string {
const orgs = token.organizationList ?? [];
return findKnownOrg(orgs) ?? '';
}
@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../types/src';
import { ICompletionsTextDocumentManagerService } from './textDocumentManager';
/**
* A tracker which can take an arbitrary number of actions to run after a given timeout
* When all pushed timeouts have been resolved, the tracker disposes of itself.
*/
export class ChangeTracker {
private _offset: number;
get offset(): number {
return this._offset;
}
private _referenceCount = 0;
private _tracker: Disposable;
private _isDisposed = false;
constructor(
fileURI: string,
insertionOffset: number,
@ICompletionsTextDocumentManagerService documentManager: ICompletionsTextDocumentManagerService
) {
this._offset = insertionOffset;
this._tracker = documentManager.onDidChangeTextDocument(e => {
if (e.document.uri === fileURI) {
for (const cc of e.contentChanges) {
if (cc.rangeOffset + cc.rangeLength <= this.offset) {
const delta = cc.text.length - cc.rangeLength;
this._offset = this._offset + delta;
}
}
}
});
}
push(action: () => void, timeout: number): void {
if (this._isDisposed) {
throw new Error('Unable to push new actions to a disposed ChangeTracker');
}
this._referenceCount++;
setTimeout(() => {
action();
this._referenceCount--;
if (this._referenceCount === 0) {
this._tracker.dispose();
this._isDisposed = true;
}
}, timeout);
}
}
@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createServiceIdentifier } from '../../../../../util/common/services';
import { Disposable, IDisposable } from '../../../../../util/vs/base/common/lifecycle';
import { IRange } from './textDocument';
export interface IPCitationDetail {
license: string;
url: string;
}
export interface IPDocumentCitation {
inDocumentUri: string;
offsetStart: number;
offsetEnd: number;
version?: number;
location?: IRange;
matchingText?: string;
details: IPCitationDetail[];
}
export const ICompletionsCitationManager = createServiceIdentifier<ICompletionsCitationManager>('ICompletionsCitationManager');
export interface ICompletionsCitationManager {
readonly _serviceBrand: undefined;
register(): IDisposable;
handleIPCodeCitation(citation: IPDocumentCitation): Promise<void>;
}
export class NoOpCitationManager implements ICompletionsCitationManager {
declare _serviceBrand: undefined;
register() { return Disposable.None; }
async handleIPCodeCitation(citation: IPDocumentCitation): Promise<void> {
// Do nothing
}
}
@@ -1,67 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import EventEmitter from 'events';
import { createServiceIdentifier } from '../../../../../util/common/services';
import { ICompletionsTelemetryService } from '../../bridge/src/completionsTelemetryServiceBridge';
import { CancellationToken, Disposable } from '../../types/src';
import { CompletionState } from './completionState';
import { GetGhostTextOptions } from './ghostText/ghostText';
import { telemetryCatch, TelemetryWithExp } from './telemetry';
import { ICompletionsPromiseQueueService } from './util/promiseQueue';
export type CompletionRequestedEvent = {
completionId: string;
completionState: CompletionState;
telemetryData: TelemetryWithExp;
cancellationToken?: CancellationToken;
options?: Partial<GetGhostTextOptions>;
};
const requestEventName = 'CompletionRequested';
export const ICompletionsNotifierService = createServiceIdentifier<ICompletionsNotifierService>('ICompletionsNotifierService');
export interface ICompletionsNotifierService {
readonly _serviceBrand: undefined;
notifyRequest(
completionState: CompletionState,
completionId: string,
telemetryData: TelemetryWithExp,
cancellationToken?: CancellationToken,
options?: Partial<GetGhostTextOptions>
): void;
onRequest(listener: (event: CompletionRequestedEvent) => void): Disposable;
}
export class CompletionNotifier implements ICompletionsNotifierService {
declare _serviceBrand: undefined;
#emitter = new EventEmitter();
constructor(
@ICompletionsPromiseQueueService protected completionsPromiseQueue: ICompletionsPromiseQueueService,
@ICompletionsTelemetryService protected completionsTelemetryService: ICompletionsTelemetryService,
) { }
notifyRequest(
completionState: CompletionState,
completionId: string,
telemetryData: TelemetryWithExp,
cancellationToken?: CancellationToken,
options?: Partial<GetGhostTextOptions>
) {
return this.#emitter.emit(requestEventName, {
completionId,
completionState,
telemetryData,
cancellationToken,
options,
});
}
onRequest(listener: (event: CompletionRequestedEvent) => void): Disposable {
const wrapper = telemetryCatch(this.completionsTelemetryService, this.completionsPromiseQueue, listener, `event.${requestEventName}`);
this.#emitter.on(requestEventName, wrapper);
return Disposable.create(() => this.#emitter.off(requestEventName, wrapper));
}
}

Some files were not shown because too many files have changed in this diff Show More