Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05b1cbf80d | |||
| ef162de168 | |||
| 1155de4867 | |||
| d452d1747e | |||
| c0d4bf8b2b | |||
| 8d89c2a0f6 | |||
| 2ad57887cd | |||
| 637456ee34 | |||
| e28125079c | |||
| ce0731c2f2 | |||
| e77f69c5c4 | |||
| 5434f3eb47 | |||
| 4a979ba7c3 | |||
| 4fe4becdd5 | |||
| 065b4ac319 | |||
| aa6133e3ed | |||
| d2b64ad5d6 | |||
| 2b79f20e19 | |||
| d9418fac98 | |||
| 0d25f4d1ef | |||
| 71a71530a3 | |||
| 9b37ca42d6 | |||
| 075eded2ba | |||
| eb6e8bbfff | |||
| 1e58c18bbc | |||
| 190bb2b756 |
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "Run",
|
||||
"command": "npm run dev",
|
||||
"icon": "play"
|
||||
}
|
||||
]
|
||||
}
|
||||
+4
-11
@@ -1,11 +1,4 @@
|
||||
VITE_API_URL=http://localhost:8000/v1/completions
|
||||
VITE_OCR_URL=http://localhost:8000/v1/ocr
|
||||
|
||||
# Ollama 配置
|
||||
OLLAMA_HOST=http://192.168.0.120:11434
|
||||
OLLAMA_MODEL=gpt-oss:120b
|
||||
|
||||
# 可选:其他配置
|
||||
# 如果ollama需要认证,可以使用以下变量
|
||||
# OLLAMA_USERNAME=your_username
|
||||
# OLLAMA_PASSWORD=your_password
|
||||
VITE_API_BASE_URL=
|
||||
VITE_API_URL=
|
||||
VITE_OCR_URL=
|
||||
VITE_CONVERT_URL=
|
||||
|
||||
+17
@@ -12,6 +12,23 @@ dist
|
||||
dist-ssr
|
||||
*.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
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# 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的最新官方文档,不要修改,涉及到前端编辑器的指令时要核对官方文档。
|
||||
|
||||
@@ -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 提出改进。
|
||||
@@ -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)
|
||||
|
||||
@@ -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 收集
|
||||
- 原始异常泄漏
|
||||
|
||||
然而,这一进展并不意味着已准备好投入生产。
|
||||
|
||||
当前仓库展示了有用的加固工作,但仍然缺乏生产服务预期的运维、测试、节流、部署和可观测性基线。
|
||||
@@ -59,7 +59,8 @@ llm-in-text/
|
||||
│ │ └── index.ts # 插件导出
|
||||
│ ├── utils/
|
||||
│ │ ├── api.js # API 调用封装
|
||||
│ │ └── config.js # 配置文件
|
||||
│ │ ├── config.js # 配置文件
|
||||
│ │ └── ocrCache.js # OCR 缓存管理
|
||||
│ ├── App.vue
|
||||
│ └── main.js
|
||||
├── backend/
|
||||
@@ -189,7 +190,7 @@ export const copilotGhostMark = $markSchema('copilot_ghost', () => ({
|
||||
flowchart LR
|
||||
A[用户输入] --> B{文档变化?}
|
||||
B -->|是| C[清除旧建议]
|
||||
C --> D[防抖 500ms]
|
||||
C --> D[防抖 1000ms]
|
||||
D --> E[发送 API 请求]
|
||||
E --> F[收到建议]
|
||||
F --> G[插入 Ghost Text]
|
||||
@@ -225,7 +226,7 @@ sequenceDiagram
|
||||
U->>E: 输入文本
|
||||
E->>P: view.update()
|
||||
P->>P: 清除旧建议
|
||||
P->>P: 防抖 500ms
|
||||
P->>P: 防抖 1000ms
|
||||
P->>A: fetchSuggestion(prefix, suffix)
|
||||
A->>B: POST /v1/completions
|
||||
B->>B: build_prompt()
|
||||
@@ -254,9 +255,10 @@ sequenceDiagram
|
||||
## 设计亮点
|
||||
|
||||
1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析
|
||||
2. **低延迟优化**:防抖机制 (500ms) + SSE 流式响应 + AbortController 取消过期请求
|
||||
2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求
|
||||
3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo
|
||||
4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好
|
||||
5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能
|
||||
|
||||
## 许可证
|
||||
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
OPENAI_API_KEY=ollama
|
||||
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
|
||||
|
||||
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.
@@ -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
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import ollama
|
||||
from dotenv import load_dotenv
|
||||
@@ -8,9 +9,14 @@ from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
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')
|
||||
|
||||
# Timeouts in seconds
|
||||
COMPLETION_TIMEOUT = 30
|
||||
OCR_TIMEOUT = 60
|
||||
CONVERT_TIMEOUT = 30
|
||||
|
||||
client = ollama.AsyncClient(host=OLLAMA_HOST)
|
||||
logger = logging.getLogger("llm")
|
||||
|
||||
@@ -54,31 +60,60 @@ def _extract_message(response) -> tuple[str, str]:
|
||||
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。
|
||||
"""
|
||||
start = time.perf_counter()
|
||||
start_dt = datetime.now()
|
||||
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,
|
||||
OLLAMA_MODEL,
|
||||
OLLAMA_HOST,
|
||||
len(prompt),
|
||||
len(system_prompt or ""),
|
||||
temperature,
|
||||
thinking,
|
||||
)
|
||||
|
||||
try:
|
||||
response = await client.chat(
|
||||
model=OLLAMA_MODEL,
|
||||
messages=[{'role': 'user', 'content': prompt}],
|
||||
stream=False,
|
||||
options={
|
||||
messages = []
|
||||
if system_prompt and system_prompt.strip():
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append({"role": "user", "content": prompt})
|
||||
|
||||
kwargs = {
|
||||
"model": OLLAMA_MODEL,
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
"options": {
|
||||
'temperature': temperature,
|
||||
'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:
|
||||
elapsed_ms = (time.perf_counter() - start) * 1000
|
||||
end_dt = datetime.now()
|
||||
@@ -112,7 +147,7 @@ async def call_ollama(prompt: str, *, tag: str = "default", temperature: float =
|
||||
if not content.strip():
|
||||
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:
|
||||
start = time.perf_counter()
|
||||
@@ -126,7 +161,8 @@ async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str:
|
||||
)
|
||||
|
||||
try:
|
||||
response = await client.chat(
|
||||
response = await asyncio.wait_for(
|
||||
client.chat(
|
||||
model=VLM_MODEL,
|
||||
messages=[{
|
||||
'role': 'user',
|
||||
@@ -135,6 +171,8 @@ async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str:
|
||||
}],
|
||||
stream=False,
|
||||
options={'temperature': 0.3}
|
||||
),
|
||||
timeout=OCR_TIMEOUT
|
||||
)
|
||||
except Exception:
|
||||
elapsed_ms = (time.perf_counter() - start) * 1000
|
||||
|
||||
+272
-34
@@ -1,14 +1,20 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse, JSONResponse
|
||||
from pydantic import BaseModel
|
||||
import json
|
||||
import asyncio
|
||||
import base64
|
||||
import uuid
|
||||
import json
|
||||
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 prompt import build_completion_prompts, prepare_prompt_context
|
||||
import markitdown
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
@@ -20,21 +26,62 @@ app = FastAPI()
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_origins=[
|
||||
"http://localhost:5173",
|
||||
"http://localhost:3000",
|
||||
"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):
|
||||
prefix: 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):
|
||||
image: str
|
||||
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:
|
||||
@@ -43,44 +90,139 @@ def _preview(text: str, limit: int = 80) -> str:
|
||||
return value
|
||||
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")
|
||||
async def create_completion(request: CompletionRequest):
|
||||
request_id = str(uuid.uuid4())[:8]
|
||||
async def create_completion(request: Request, req: CompletionRequest):
|
||||
request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4())
|
||||
request_tag = request_id[:8]
|
||||
inference_task: Optional[asyncio.Task] = None
|
||||
|
||||
try:
|
||||
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,
|
||||
len(request.prefix or ""),
|
||||
len(request.suffix or ""),
|
||||
request.languageId,
|
||||
_preview((request.prefix or "")[-120:]),
|
||||
_preview((request.suffix or "")[:120]),
|
||||
len(req.prefix or ""),
|
||||
len(req.suffix or ""),
|
||||
req.languageId,
|
||||
req.model_thinking,
|
||||
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 ""
|
||||
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(
|
||||
"[%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,
|
||||
len(content),
|
||||
_preview(content, 120),
|
||||
)
|
||||
|
||||
async def generate():
|
||||
yield f"data: {json.dumps({'content': content})}\n\n"
|
||||
yield f"data: {json.dumps({'done': True})}\n\n"
|
||||
yield _sse_payload({"content": content})
|
||||
yield _sse_payload({"done": True})
|
||||
|
||||
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:
|
||||
logger.exception("[%s] /v1/completions failed: %s", request_id, e)
|
||||
return JSONResponse(content={"error": str(e)}, status_code=500)
|
||||
logger.exception("[%s] /v1/completions failed request_id=%s: %s", request_tag, request_id, e)
|
||||
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")
|
||||
async def ocr_image(request: OCRRequest):
|
||||
@@ -93,7 +235,22 @@ async def ocr_image(request: OCRRequest):
|
||||
request.language,
|
||||
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)
|
||||
|
||||
# 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))
|
||||
result = await call_vlm_ocr(image_bytes, request.language)
|
||||
logger.info(
|
||||
@@ -105,8 +262,89 @@ async def ocr_image(request: OCRRequest):
|
||||
return {"text": result, "filename": request.filename}
|
||||
except Exception as 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__":
|
||||
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
@@ -1,5 +1,41 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import re
|
||||
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:
|
||||
if not language_id:
|
||||
return "markdown"
|
||||
@@ -11,70 +47,537 @@ def _sanitize_language_id(language_id: str) -> str:
|
||||
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]:
|
||||
"""
|
||||
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]:
|
||||
return _prepare_context(prefix, suffix)
|
||||
|
||||
|
||||
def build_prompt(prefix: str, suffix: str, language_id: str = "markdown") -> str:
|
||||
safe_language_id = _sanitize_language_id(language_id)
|
||||
recent_prefix, recent_suffix = _prepare_context(prefix, suffix)
|
||||
LANGUAGE_SYNONYMS = {
|
||||
"md": "markdown",
|
||||
"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:
|
||||
- Return ONLY the text that should be inserted at the cursor between PREFIX and SUFFIX.
|
||||
- Prefer a meaningful, non-empty insertion with moderate length.
|
||||
- Avoid overly short outputs with little information value.
|
||||
def _canonical_language_id(language_id: str) -> str:
|
||||
safe = _sanitize_language_id(language_id).lower()
|
||||
if not safe:
|
||||
return "markdown"
|
||||
return LANGUAGE_SYNONYMS.get(safe, safe)
|
||||
|
||||
Important context:
|
||||
- PREFIX may contain OCR metadata inline after images, e.g.  <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:
|
||||
1. Seamless join:
|
||||
PREFIX + OUTPUT + SUFFIX must read naturally as one continuous document.
|
||||
2. No suffix repetition:
|
||||
Do NOT repeat text that already appears at the start of SUFFIX.
|
||||
3. Balanced length:
|
||||
Prefer concise but meaningful continuation, not ultra-short fragments.
|
||||
Default target is 20-120 characters and 1-3 lines for plain prose.
|
||||
You may be longer when structure requires it (lists, tables, code blocks, math blocks).
|
||||
4. Avoid trivial output:
|
||||
Do not output only punctuation or filler such as ".", ",", ";", ":".
|
||||
Do not output just one token unless it is structurally necessary.
|
||||
5. Preserve local style:
|
||||
Match nearby language, tone, punctuation, spacing, and indentation.
|
||||
6. Markdown awareness:
|
||||
Continue active list/checkbox/ordered-list patterns when applicable.
|
||||
Preserve indentation in nested list/code contexts.
|
||||
You may output full markdown structures when context needs them: headings, lists, tables, fenced code blocks, blockquotes, and LaTeX ($...$ / $$...$$).
|
||||
Close obvious unclosed inline markdown markers only when needed to bridge.
|
||||
7. Strict output format:
|
||||
Output insertion text only.
|
||||
No explanations, labels, or wrapper quotes around the whole output.
|
||||
Markdown syntax is allowed when it is the intended insertion (including fenced code blocks and LaTeX).
|
||||
def _language_guidance(language_id: str) -> str:
|
||||
canonical = _canonical_language_id(language_id)
|
||||
if canonical == "markdown":
|
||||
return ""
|
||||
if canonical == "mermaid":
|
||||
return """
|
||||
Language-specific guidance (mermaid):
|
||||
- Output valid Mermaid syntax only.
|
||||
- Prefer concise, syntactically correct diagram statements.
|
||||
- Avoid prose unless the user prompt explicitly requires it."""
|
||||
if canonical == "latex":
|
||||
return """
|
||||
Language-specific guidance (latex):
|
||||
- Output LaTeX math content only when completing LaTeX.
|
||||
- If CURSOR_IN_FENCED_CODE_BLOCK=true and CURSOR_FENCE_LANGUAGE is latex/tex/katex:
|
||||
- Output raw LaTeX lines only.
|
||||
- Do not wrap with $ or $$."""
|
||||
if canonical == "json":
|
||||
return """
|
||||
Language-specific guidance (json):
|
||||
- Output strict JSON only (no comments, no trailing commas).
|
||||
- Ensure valid quotes and braces."""
|
||||
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>
|
||||
<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>
|
||||
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> <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.  <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.
|
||||
|
||||
@@ -88,7 +591,27 @@ Now produce the insertion.
|
||||
|
||||
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
|
||||
|
||||
@@ -4,3 +4,9 @@ ollama
|
||||
pydantic
|
||||
python-dotenv
|
||||
httpx
|
||||
geoip2
|
||||
markitdown[all]
|
||||
python-docx
|
||||
python-pptx
|
||||
openpyxl
|
||||
pypdf
|
||||
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
@@ -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 == {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
-49
@@ -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;
|
||||
}
|
||||
}
|
||||
-101
@@ -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,
|
||||
};
|
||||
-166
@@ -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';
|
||||
-5928
File diff suppressed because one or more lines are too long
-3011
File diff suppressed because it is too large
Load Diff
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
-4671
File diff suppressed because it is too large
Load Diff
-5927
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
Reference in New Issue
Block a user