✨ feat(vscode): 代码阅读的词可以讲源码里别处的名字(Segment.refers):FastAPI 路径里的 {user_id} 讲函数的参数,单击参数时跟着高亮,⌘ + 点击跳到参数
This commit is contained in:
1 parent
25569fa506
commit
15790ce7c8
9 files changed
+82
-22
No files matched your search
@@ -275,8 +275,11 @@ export type TokenRole
|
||||
* 它在源码里的起始下标(同 SourceSpan 的下标),预览拿它向 VS Code 查定义、跳过去;译词、提示词没有。
|
||||
* reference:只有库规则给的段有,这段讲的东西由规则自己查定义(LibraryRule.resolve),不问语言扩展;这样的段必须带 offset。
|
||||
* color:这段自己的颜色(暗色、亮色主题各一个),盖过词性的颜色:库规则按库自己的配色上色(FastAPI 的 HTTP 方法用 Swagger UI 的颜色)。
|
||||
* refers:这段讲的是源码里别处的一个名字(那个名字的范围),语言扩展不认得这段自己的位置:写在字符串里的名字
|
||||
* (FastAPI 路径 `"/{user_id}"` 里的 user_id 讲的是函数的参数 user_id)。预览把这段当那个名字:⌘ / Ctrl + 点击跳到它的定义,
|
||||
* 单击高亮它的各处出现,那个名字的某处出现盖住 refers 时这段也跟着高亮。offset 照旧是这段照抄的位置。
|
||||
*/
|
||||
export interface Segment { text: string, role: TokenRole, offset?: number, reference?: RuleReference, color?: ThemeColor }
|
||||
export interface Segment { text: string, role: TokenRole, offset?: number, reference?: RuleReference, color?: ThemeColor, refers?: SourceRange }
|
||||
|
||||
/** 框的样式,决定框头的图标和框的颜色:'iteration' 是写在表达式里的遍历,图标、颜色同「循环」块。 */
|
||||
export type GroupStyle = 'iteration'
|
||||
|
||||
@@ -95,9 +95,12 @@ describe('fastapi 库规则:接口卡片', () => {
|
||||
const rows = properties(await route(ROUTES, 'update_user', known({ Forbidden: '403' })))
|
||||
const request = rows.find(row => row.label === '请求')
|
||||
const [url, method] = request?.items ?? []
|
||||
expect(flatSegments(url.cells[1]).map(piece => [piece.text, piece.role])).toEqual([['/users', 'string'], ['/', 'string'], ['{user_id}', 'variable']])
|
||||
expect(flatSegments(url.cells[1]).map(piece => [piece.text, piece.role])).toEqual([['/users', 'string'], ['/', 'string'], ['{', 'variable'], ['user_id', 'variable'], ['}', 'variable']])
|
||||
for (const piece of flatSegments(url.cells[1]))
|
||||
expect(ROUTES.slice(piece.offset, (piece.offset ?? 0) + piece.text.length)).toBe(piece.text)
|
||||
// 路径里的 user_id 讲的是 update_user 的参数 user_id:高亮参数的各处出现时跟着亮,⌘ + 点击跳到参数
|
||||
const param = ROUTES.indexOf('user_id: Int4Path', ROUTES.indexOf('def update_user'))
|
||||
expect(flatSegments(url.cells[1]).find(piece => piece.text === 'user_id')?.refers).toEqual({ startIndex: param, endIndex: param + 'user_id'.length })
|
||||
expect(method.cells[1]).toEqual([{ text: 'PUT', role: 'keyword', color: { dark: '#fca130', light: '#b35f00' } }])
|
||||
const [ok, forbidden, validation] = rows.find(row => row.label === '响应')?.items ?? []
|
||||
expect(ok.cells[0]).toEqual([{ text: '200', role: 'number', color: { dark: '#2ea043', light: '#1a7f37' } }])
|
||||
|
||||
@@ -266,8 +266,11 @@ function routeOf(decorator: Node, routers: ReadonlyMap<string, Router>): Route |
|
||||
return { decorator, router, method: method.toUpperCase(), call, path: call.positional[0] ?? call.keywords.get('path') }
|
||||
}
|
||||
|
||||
/** 路径里的一段:`{参数}` 按变量着色,其余按字符串,带源码下标;写的不是字符串时交给翻译插件翻。 */
|
||||
function pathSegments(node: Node | undefined, env: Env): Inline[] {
|
||||
/**
|
||||
* 路径里的一段:`{参数}` 按变量着色,其余按字符串,都带源码下标;花括号里的名字讲的是函数的同名参数(params),
|
||||
* 标上 refers,高亮参数的各处出现时它也跟着亮、⌘ + 点击跳到参数。写的不是字符串时交给翻译插件翻。
|
||||
*/
|
||||
function pathSegments(node: Node | undefined, params: ReadonlyMap<string, Node>, env: Env): Inline[] {
|
||||
if (!node)
|
||||
return []
|
||||
const content = stringContent(node)
|
||||
@@ -276,7 +279,22 @@ function pathSegments(node: Node | undefined, env: Env): Inline[] {
|
||||
const segments: Segment[] = []
|
||||
let at = content.node?.startIndex ?? node.startIndex
|
||||
for (const part of content.text.split(/(\{[^}]*\})/).filter(Boolean)) {
|
||||
segments.push({ text: part, role: part.startsWith('{') ? 'variable' : 'string', offset: at })
|
||||
if (!part.startsWith('{')) {
|
||||
segments.push({ text: part, role: 'string', offset: at })
|
||||
}
|
||||
else {
|
||||
// `{file_path:path}`:冒号前面是参数名,后面是转换器
|
||||
const colon = part.indexOf(':')
|
||||
const key = part.slice(1, colon < 0 ? -1 : colon)
|
||||
const rest = part.slice(1 + key.length, -1)
|
||||
const param = params.get(key.trim())
|
||||
const pieces: Segment[] = [
|
||||
{ text: '{', role: 'variable', offset: at },
|
||||
{ text: key, role: 'variable', offset: at + 1, ...(param && { refers: range(param) }) },
|
||||
{ text: `${rest}}`, role: 'variable', offset: at + 1 + key.length },
|
||||
]
|
||||
segments.push(...pieces.filter(piece => piece.text !== ''))
|
||||
}
|
||||
at += part.length
|
||||
}
|
||||
return segments
|
||||
@@ -537,7 +555,8 @@ function routeCard(block: FunctionBlock, route: Route, env: Env): RuleCard {
|
||||
const at = (place: Place): Placed[] => placed.filter(item => item.place === place)
|
||||
const body = at('body')
|
||||
|
||||
const url = [...pathSegments(router.prefix, env), ...pathSegments(route.path, env)]
|
||||
const params = new Map(facts.parameters.flatMap(({ name: param }) => param.type === 'identifier' ? [[param.text, param] as const] : []))
|
||||
const url = [...pathSegments(router.prefix, params, env), ...pathSegments(route.path, params, env)]
|
||||
const request: PropertyItem[] = [
|
||||
requestItem(words.items.url, url),
|
||||
requestItem(words.items.method, [{ text: route.method, role: 'keyword', ...(METHOD_COLORS[route.method] && { color: METHOD_COLORS[route.method] }) }]),
|
||||
|
||||
Reference in new issue
Block a user