mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-10 07:19:02 +08:00
refactor(CodeEditor): support file extensions explicitly (#9707)
This commit is contained in:
parent
1ee57f1385
commit
4dbe5c8055
@ -0,0 +1,43 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { getNormalizedExtension } from '../utils'
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
getExtensionByLanguage: vi.fn()
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@renderer/utils/code-language', () => ({
|
||||||
|
getExtensionByLanguage: mocks.getExtensionByLanguage
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('getNormalizedExtension', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return custom mapping for custom language', async () => {
|
||||||
|
mocks.getExtensionByLanguage.mockReturnValue(undefined)
|
||||||
|
await expect(getNormalizedExtension('svg')).resolves.toBe('xml')
|
||||||
|
await expect(getNormalizedExtension('SVG')).resolves.toBe('xml')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should prefer custom mapping when both custom and linguist exist', async () => {
|
||||||
|
mocks.getExtensionByLanguage.mockReturnValue('.svg')
|
||||||
|
await expect(getNormalizedExtension('svg')).resolves.toBe('xml')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return linguist mapping when available (strip leading dot)', async () => {
|
||||||
|
mocks.getExtensionByLanguage.mockReturnValue('.ts')
|
||||||
|
await expect(getNormalizedExtension('TypeScript')).resolves.toBe('ts')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return extension when input already looks like extension (leading dot)', async () => {
|
||||||
|
mocks.getExtensionByLanguage.mockReturnValue(undefined)
|
||||||
|
await expect(getNormalizedExtension('.json')).resolves.toBe('json')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return language as-is when no rules matched', async () => {
|
||||||
|
mocks.getExtensionByLanguage.mockReturnValue(undefined)
|
||||||
|
await expect(getNormalizedExtension('unknownLanguage')).resolves.toBe('unknownLanguage')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -8,7 +8,9 @@ import { getNormalizedExtension } from './utils'
|
|||||||
|
|
||||||
const logger = loggerService.withContext('CodeEditorHooks')
|
const logger = loggerService.withContext('CodeEditorHooks')
|
||||||
|
|
||||||
// 语言对应的 linter 加载器
|
/** 语言对应的 linter 加载器
|
||||||
|
* key: 语言文件扩展名(不包含 `.`)
|
||||||
|
*/
|
||||||
const linterLoaders: Record<string, () => Promise<any>> = {
|
const linterLoaders: Record<string, () => Promise<any>> = {
|
||||||
json: async () => {
|
json: async () => {
|
||||||
const jsonParseLinter = await import('@codemirror/lang-json').then((mod) => mod.jsonParseLinter)
|
const jsonParseLinter = await import('@codemirror/lang-json').then((mod) => mod.jsonParseLinter)
|
||||||
@ -64,13 +66,15 @@ async function loadLanguageExtension(language: string): Promise<Extension | null
|
|||||||
* 加载 linter 扩展
|
* 加载 linter 扩展
|
||||||
*/
|
*/
|
||||||
async function loadLinterExtension(language: string): Promise<Extension | null> {
|
async function loadLinterExtension(language: string): Promise<Extension | null> {
|
||||||
const loader = linterLoaders[language]
|
const fileExt = await getNormalizedExtension(language)
|
||||||
|
|
||||||
|
const loader = linterLoaders[fileExt]
|
||||||
if (!loader) return null
|
if (!loader) return null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await loader()
|
return await loader()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug(`Failed to load linter for ${language}`, error as Error)
|
logger.debug(`Failed to load linter for ${language} (${fileExt})`, error as Error)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,13 @@ export interface CodeEditorProps {
|
|||||||
value: string
|
value: string
|
||||||
/** Placeholder when the editor content is empty. */
|
/** Placeholder when the editor content is empty. */
|
||||||
placeholder?: string | HTMLElement
|
placeholder?: string | HTMLElement
|
||||||
/** Code language, supports aliases. */
|
/**
|
||||||
|
* Code language string.
|
||||||
|
* - Case-insensitive.
|
||||||
|
* - Supports common names: javascript, json, python, etc.
|
||||||
|
* - Supports aliases: c#/csharp, objective-c++/obj-c++/objc++, etc.
|
||||||
|
* - Supports file extensions: .cpp/cpp, .js/js, .py/py, etc.
|
||||||
|
*/
|
||||||
language: string
|
language: string
|
||||||
/** Fired when ref.save() is called or the save shortcut is triggered. */
|
/** Fired when ref.save() is called or the save shortcut is triggered. */
|
||||||
onSave?: (newContent: string) => void
|
onSave?: (newContent: string) => void
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const _customLanguageExtensions: Record<string, string> = {
|
|||||||
* 获取语言的扩展名,用于 @uiw/codemirror-extensions-langs
|
* 获取语言的扩展名,用于 @uiw/codemirror-extensions-langs
|
||||||
* - 先搜索自定义扩展名
|
* - 先搜索自定义扩展名
|
||||||
* - 再搜索 github linguist 扩展名
|
* - 再搜索 github linguist 扩展名
|
||||||
|
* - 最后假定名称已经是扩展名
|
||||||
* @param language 语言名称
|
* @param language 语言名称
|
||||||
* @returns 扩展名(不包含 `.`)
|
* @returns 扩展名(不包含 `.`)
|
||||||
*/
|
*/
|
||||||
@ -29,6 +30,11 @@ export async function getNormalizedExtension(language: string) {
|
|||||||
return linguistExt.slice(1)
|
return linguistExt.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果语言名称像扩展名
|
||||||
|
if (language.startsWith('.') && language.length > 1) {
|
||||||
|
return language.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
// 回退到语言名称
|
// 回退到语言名称
|
||||||
return language
|
return language
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user