语言服务 API
Trae IDE 的语言服务 API 提供了丰富的语言功能支持,包括智能感知、代码补全、语法高亮、错误检查等。
概述
语言服务功能包括:
- 代码补全和智能感知
- 语法高亮和语义着色
- 错误检查和诊断
- 代码导航和引用查找
- 重构和代码操作
- 悬停信息和文档
主要接口
CompletionItemProvider
typescript
interface CompletionItemProvider {
provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken,
context: CompletionContext
): ProviderResult<CompletionItem[] | CompletionList>
resolveCompletionItem?(
item: CompletionItem,
token: CancellationToken
): ProviderResult<CompletionItem>
}HoverProvider
typescript
interface HoverProvider {
provideHover(
document: TextDocument,
position: Position,
token: CancellationToken
): ProviderResult<Hover>
}DefinitionProvider
typescript
interface DefinitionProvider {
provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken
): ProviderResult<Definition | LocationLink[]>
}注册语言服务
基本注册
typescript
import { languages, CompletionItemKind, SnippetString } from 'trae-api'
// 注册代码补全提供者
const completionProvider = languages.registerCompletionItemProvider(
'javascript', // 语言标识符
{
provideCompletionItems(document, position, token, context) {
// 提供补全项
const completions = [
{
label: 'console.log',
kind: CompletionItemKind.Function,
insertText: new SnippetString('console.log(${1:message})'),
documentation: '在控制台输出信息'
}
]
return completions
}
},
'.' // 触发字符
)
// 注册悬停提供者
const hoverProvider = languages.registerHoverProvider('javascript', {
provideHover(document, position, token) {
const range = document.getWordRangeAtPosition(position)
const word = document.getText(range)
if (word === 'console') {
return {
contents: ['**Console API**', '用于调试和日志输出的全局对象']
}
}
}
})多语言支持
typescript
// 支持多种语言
const selector: DocumentSelector = [
{ scheme: 'file', language: 'javascript' },
{ scheme: 'file', language: 'typescript' },
{ scheme: 'file', language: 'javascriptreact' },
{ scheme: 'file', language: 'typescriptreact' }
]
const provider = languages.registerCompletionItemProvider(
selector,
new MyCompletionProvider(),
'.', '(', ':'
)代码补全
基础补全提供者
typescript
class JavaScriptCompletionProvider implements CompletionItemProvider {
provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken,
context: CompletionContext
): CompletionItem[] {
const completions: CompletionItem[] = []
// 获取当前行文本
const lineText = document.lineAt(position).text
const linePrefix = lineText.substring(0, position.character)
// 根据上下文提供不同的补全
if (linePrefix.endsWith('console.')) {
completions.push(...this.getConsoleCompletions())
} else if (linePrefix.includes('function ')) {
completions.push(...this.getFunctionCompletions())
} else {
completions.push(...this.getGeneralCompletions())
}
return completions
}
private getConsoleCompletions(): CompletionItem[] {
return [
{
label: 'log',
kind: CompletionItemKind.Method,
insertText: new SnippetString('log(${1:message})'),
documentation: '输出日志信息',
detail: 'console.log(message: any): void'
},
{
label: 'error',
kind: CompletionItemKind.Method,
insertText: new SnippetString('error(${1:message})'),
documentation: '输出错误信息',
detail: 'console.error(message: any): void'
},
{
label: 'warn',
kind: CompletionItemKind.Method,
insertText: new SnippetString('warn(${1:message})'),
documentation: '输出警告信息',
detail: 'console.warn(message: any): void'
}
]
}
private getFunctionCompletions(): CompletionItem[] {
return [
{
label: 'async function',
kind: CompletionItemKind.Snippet,
insertText: new SnippetString('async function ${1:name}(${2:params}) {\n\t${3:// TODO: implement}\n}'),
documentation: '创建异步函数',
sortText: '0' // 优先显示
}
]
}
private getGeneralCompletions(): CompletionItem[] {
return [
{
label: 'if',
kind: CompletionItemKind.Keyword,
insertText: new SnippetString('if (${1:condition}) {\n\t${2:// TODO}\n}'),
documentation: '条件语句'
},
{
label: 'for',
kind: CompletionItemKind.Keyword,
insertText: new SnippetString('for (let ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {\n\t${3:// TODO}\n}'),
documentation: '循环语句'
}
]
}
resolveCompletionItem(item: CompletionItem, token: CancellationToken): CompletionItem {
// 延迟加载详细信息
if (item.label === 'log') {
item.documentation = new MarkdownString(`
**console.log()**
输出信息到控制台。
\`\`\`javascript
console.log('Hello, World!')
console.log('Value:', value)
\`\`\`
`)
}
return item
}
}智能补全
typescript
class IntelligentCompletionProvider implements CompletionItemProvider {
private symbolCache = new Map<string, CompletionItem[]>()
async provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken,
context: CompletionContext
): Promise<CompletionItem[]> {
const completions: CompletionItem[] = []
// 分析当前上下文
const analysis = await this.analyzeContext(document, position)
// 根据上下文类型提供补全
switch (analysis.type) {
case 'property-access':
completions.push(...await this.getPropertyCompletions(analysis.object))
break
case 'function-call':
completions.push(...await this.getFunctionCompletions(analysis.function))
break
case 'import-statement':
completions.push(...await this.getImportCompletions(analysis.module))
break
default:
completions.push(...await this.getContextualCompletions(document, position))
}
return completions
}
private async analyzeContext(document: TextDocument, position: Position) {
const lineText = document.lineAt(position).text
const beforeCursor = lineText.substring(0, position.character)
// 属性访问
const propertyMatch = beforeCursor.match(/(\w+)\.(\w*)$/)
if (propertyMatch) {
return {
type: 'property-access',
object: propertyMatch[1],
property: propertyMatch[2]
}
}
// 函数调用
const functionMatch = beforeCursor.match(/(\w+)\(\s*$/)
if (functionMatch) {
return {
type: 'function-call',
function: functionMatch[1]
}
}
// 导入语句
const importMatch = beforeCursor.match(/import\s+.*from\s+['"]([^'"]*$)/)
if (importMatch) {
return {
type: 'import-statement',
module: importMatch[1]
}
}
return { type: 'general' }
}
private async getPropertyCompletions(objectName: string): Promise<CompletionItem[]> {
// 根据对象类型返回属性补全
const objectType = await this.inferObjectType(objectName)
switch (objectType) {
case 'Array':
return this.getArrayMethods()
case 'String':
return this.getStringMethods()
case 'Object':
return this.getObjectMethods()
default:
return []
}
}
private getArrayMethods(): CompletionItem[] {
return [
{
label: 'push',
kind: CompletionItemKind.Method,
insertText: new SnippetString('push(${1:element})'),
documentation: '向数组末尾添加元素',
detail: 'push(...items: T[]): number'
},
{
label: 'map',
kind: CompletionItemKind.Method,
insertText: new SnippetString('map(${1:item} => ${2:item})'),
documentation: '创建一个新数组,包含调用函数的结果',
detail: 'map<U>(callbackfn: (value: T) => U): U[]'
},
{
label: 'filter',
kind: CompletionItemKind.Method,
insertText: new SnippetString('filter(${1:item} => ${2:condition})'),
documentation: '创建一个新数组,包含通过测试的元素',
detail: 'filter(predicate: (value: T) => boolean): T[]'
}
]
}
private async inferObjectType(objectName: string): Promise<string> {
// 简单的类型推断逻辑
if (objectName.includes('array') || objectName.includes('list')) {
return 'Array'
}
if (objectName.includes('string') || objectName.includes('text')) {
return 'String'
}
return 'Object'
}
}悬停信息
悬停提供者
typescript
class DocumentationHoverProvider implements HoverProvider {
private documentationCache = new Map<string, MarkdownString>()
async provideHover(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Hover | undefined> {
const range = document.getWordRangeAtPosition(position)
if (!range) {
return undefined
}
const word = document.getText(range)
const documentation = await this.getDocumentation(word, document, position)
if (documentation) {
return new Hover(documentation, range)
}
return undefined
}
private async getDocumentation(
word: string,
document: TextDocument,
position: Position
): Promise<MarkdownString | undefined> {
// 检查缓存
if (this.documentationCache.has(word)) {
return this.documentationCache.get(word)
}
// 分析符号类型
const symbolInfo = await this.analyzeSymbol(word, document, position)
if (symbolInfo) {
const markdown = this.createDocumentation(symbolInfo)
this.documentationCache.set(word, markdown)
return markdown
}
return undefined
}
private async analyzeSymbol(
word: string,
document: TextDocument,
position: Position
) {
// 获取符号定义
const definition = await this.findDefinition(word, document)
if (definition) {
return {
name: word,
type: definition.type,
description: definition.description,
signature: definition.signature,
examples: definition.examples
}
}
// 检查是否是内置 API
const builtinInfo = this.getBuiltinInfo(word)
if (builtinInfo) {
return builtinInfo
}
return null
}
private createDocumentation(symbolInfo: any): MarkdownString {
const markdown = new MarkdownString()
// 添加签名
if (symbolInfo.signature) {
markdown.appendCodeblock(symbolInfo.signature, 'javascript')
}
// 添加描述
if (symbolInfo.description) {
markdown.appendMarkdown(`\n${symbolInfo.description}\n`)
}
// 添加示例
if (symbolInfo.examples && symbolInfo.examples.length > 0) {
markdown.appendMarkdown('\n**示例:**\n')
symbolInfo.examples.forEach((example: string) => {
markdown.appendCodeblock(example, 'javascript')
})
}
// 添加链接
if (symbolInfo.documentation) {
markdown.appendMarkdown(`\n[查看文档](${symbolInfo.documentation})`)
}
return markdown
}
private getBuiltinInfo(word: string) {
const builtins: Record<string, any> = {
'console': {
name: 'console',
type: 'object',
description: '控制台对象,用于调试和日志输出',
signature: 'console: Console',
examples: [
'console.log("Hello, World!")',
'console.error("Something went wrong")'
]
},
'setTimeout': {
name: 'setTimeout',
type: 'function',
description: '在指定的延迟后执行函数',
signature: 'setTimeout(callback: Function, delay: number): number',
examples: [
'setTimeout(() => console.log("Hello"), 1000)'
]
}
}
return builtins[word]
}
private async findDefinition(word: string, document: TextDocument) {
// 简单的定义查找逻辑
const text = document.getText()
const functionRegex = new RegExp(`function\\s+${word}\\s*\\([^)]*\\)`, 'g')
const variableRegex = new RegExp(`(?:let|const|var)\\s+${word}\\s*=`, 'g')
if (functionRegex.test(text)) {
return {
type: 'function',
description: `用户定义的函数 ${word}`,
signature: `function ${word}(...)`
}
}
if (variableRegex.test(text)) {
return {
type: 'variable',
description: `用户定义的变量 ${word}`,
signature: `${word}: any`
}
}
return null
}
}定义和引用
定义提供者
typescript
class DefinitionProvider implements vscode.DefinitionProvider {
async provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<Definition | undefined> {
const range = document.getWordRangeAtPosition(position)
if (!range) {
return undefined
}
const word = document.getText(range)
const definitions = await this.findDefinitions(word, document)
return definitions
}
private async findDefinitions(word: string, document: TextDocument): Promise<Location[]> {
const definitions: Location[] = []
const text = document.getText()
const lines = text.split('\n')
// 查找函数定义
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
const functionMatch = line.match(new RegExp(`function\\s+${word}\\s*\\(`))
if (functionMatch) {
const position = new Position(i, functionMatch.index!)
definitions.push(new Location(document.uri, position))
}
// 查找变量定义
const variableMatch = line.match(new RegExp(`(?:let|const|var)\\s+${word}\\s*=`))
if (variableMatch) {
const position = new Position(i, variableMatch.index!)
definitions.push(new Location(document.uri, position))
}
}
return definitions
}
}引用提供者
typescript
class ReferenceProvider implements vscode.ReferenceProvider {
async provideReferences(
document: TextDocument,
position: Position,
context: ReferenceContext,
token: CancellationToken
): Promise<Location[]> {
const range = document.getWordRangeAtPosition(position)
if (!range) {
return []
}
const word = document.getText(range)
const references = await this.findReferences(word, document, context.includeDeclaration)
return references
}
private async findReferences(
word: string,
document: TextDocument,
includeDeclaration: boolean
): Promise<Location[]> {
const references: Location[] = []
const text = document.getText()
const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
const regex = new RegExp(`\\b${word}\\b`, 'g')
let match
while ((match = regex.exec(line)) !== null) {
const position = new Position(i, match.index)
// 检查是否是定义
const isDefinition = this.isDefinition(line, match.index, word)
if (includeDeclaration || !isDefinition) {
references.push(new Location(document.uri, position))
}
}
}
return references
}
private isDefinition(line: string, index: number, word: string): boolean {
const beforeWord = line.substring(0, index)
// 检查是否是函数定义
if (beforeWord.match(/function\s*$/)) {
return true
}
// 检查是否是变量定义
if (beforeWord.match(/(?:let|const|var)\s*$/)) {
return true
}
return false
}
}实用示例
TypeScript 语言服务
typescript
class TypeScriptLanguageService {
private completionProvider: Disposable
private hoverProvider: Disposable
private definitionProvider: Disposable
private diagnosticCollection: DiagnosticCollection
constructor() {
this.setupProviders()
this.diagnosticCollection = languages.createDiagnosticCollection('typescript')
}
private setupProviders() {
const selector = { scheme: 'file', language: 'typescript' }
// 注册补全提供者
this.completionProvider = languages.registerCompletionItemProvider(
selector,
new TypeScriptCompletionProvider(),
'.', '(', '<'
)
// 注册悬停提供者
this.hoverProvider = languages.registerHoverProvider(
selector,
new TypeScriptHoverProvider()
)
// 注册定义提供者
this.definitionProvider = languages.registerDefinitionProvider(
selector,
new TypeScriptDefinitionProvider()
)
// 监听文档变化进行诊断
workspace.onDidChangeTextDocument(event => {
if (event.document.languageId === 'typescript') {
this.validateDocument(event.document)
}
})
}
private async validateDocument(document: TextDocument) {
const diagnostics = await this.getDiagnostics(document)
this.diagnosticCollection.set(document.uri, diagnostics)
}
private async getDiagnostics(document: TextDocument): Promise<Diagnostic[]> {
// 简单的 TypeScript 语法检查
const diagnostics: Diagnostic[] = []
const text = document.getText()
const lines = text.split('\n')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
// 检查未声明的变量
const undeclaredMatch = line.match(/\b(\w+)\s*=/)
if (undeclaredMatch && !line.includes('let') && !line.includes('const') && !line.includes('var')) {
const diagnostic = new Diagnostic(
new Range(i, undeclaredMatch.index!, i, undeclaredMatch.index! + undeclaredMatch[1].length),
`变量 '${undeclaredMatch[1]}' 未声明`,
DiagnosticSeverity.Error
)
diagnostic.source = 'TypeScript'
diagnostics.push(diagnostic)
}
}
return diagnostics
}
dispose() {
this.completionProvider.dispose()
this.hoverProvider.dispose()
this.definitionProvider.dispose()
this.diagnosticCollection.dispose()
}
}最佳实践
- 性能优化: 使用缓存和延迟加载减少计算开销
- 用户体验: 提供准确和有用的补全建议
- 错误处理: 妥善处理语言分析中的异常情况
- 扩展性: 设计可扩展的架构支持多种语言特性
- 一致性: 保持与 IDE 其他语言服务的一致性