Merge pull request #120 from yanzhaohui1999/feat/editor-languages

feat(editor): add C# / Kotlin / Swift syntax highlightingFeat/editor languages
This commit is contained in:
Atlas Zeng
2026-08-16 16:23:11 +08:00
committed by GitHub
3 changed files with 41 additions and 3 deletions
+8
View File
@@ -23,6 +23,8 @@ import { toml } from '@codemirror/legacy-modes/mode/toml'
import { nginx } from '@codemirror/legacy-modes/mode/nginx'
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile'
import { properties } from '@codemirror/legacy-modes/mode/properties'
import { csharp, kotlin } from '@codemirror/legacy-modes/mode/clike'
import { swift } from '@codemirror/legacy-modes/mode/swift'
/** The lowercased file extension of a path ('' when none). */
export function extOf(path: string): string {
@@ -48,6 +50,9 @@ export function languageKeyForExt(ext: string): string | null {
case 'yaml': case 'yml': return 'yaml'
case 'sql': return 'sql'
case 'java': return 'java'
case 'cs': return 'csharp'
case 'kt': case 'kts': return 'kotlin'
case 'swift': return 'swift'
case 'c': case 'h': return 'c'
case 'cc': case 'cpp': case 'cxx': case 'hpp': case 'hh': case 'hxx': return 'cpp'
case 'rs': return 'rust'
@@ -76,6 +81,9 @@ const FACTORIES: Record<string, () => Language | LanguageSupport> = {
yaml: () => yaml(),
sql: () => sql(),
java: () => java(),
csharp: () => StreamLanguage.define(csharp),
kotlin: () => StreamLanguage.define(kotlin),
swift: () => StreamLanguage.define(swift),
c: () => cpp(),
cpp: () => cpp(),
rust: () => rust(),
+30 -3
View File
@@ -12,7 +12,7 @@ import { SettingsConflictError, settingsNamespace } from '@deepseek-ai/dsh-setti
import { apply, mediaTypeForPath } from '../src/index.ts'
import * as git from '../src/git.ts'
import { listDirectory } from '../src/fs-tree.ts'
import { defaultShell, PtyManager } from '../src/pty-manager.ts'
import { defaultShell, PtyManager, type SidebarPty } from '../src/pty-manager.ts'
import type { SidebarWebRoute, SidebarWebUpgradeRoute } from '../src/context-types.ts'
interface FakeContext {
@@ -31,6 +31,32 @@ interface FakeContext {
get: (key: string) => undefined
}
/**
* The login-shell test spawns a real pty whose bash may still be writing to
* the temp HOME (history files, etc.) when `disposeAll()` returns — `close()`
* only requests the kill and the process exit lands asynchronously in
* `onExit`. Deleting the directory immediately then races the shell and
* fails with ENOTEMPTY on CI. Wait for the spawned handle to report `exited`
* (bounded), then remove with a short retry loop as a belt-and-braces
* fallback for any straggler fd.
*/
async function rmTempDirAfterPtyExit(handle: { exited: boolean }, dir: string): Promise<void> {
const deadline = Date.now() + 2000
while (Date.now() < deadline && !handle.exited) {
await new Promise((resolve) => setTimeout(resolve, 50))
}
for (let attempt = 0; ; attempt++) {
try {
rmSync(dir, { recursive: true, force: true })
return
} catch (error) {
const busy = (error as NodeJS.ErrnoException).code === 'ENOTEMPTY' || (error as NodeJS.ErrnoException).code === 'EBUSY'
if (!busy || attempt >= 4) throw error
await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)))
}
}
}
describe('host plugin smoke', () => {
it('serves PDF with the browser-native content type', () => {
expect(mediaTypeForPath('/work/report.PDF')).toBe('application/pdf')
@@ -191,6 +217,7 @@ describe('host plugin smoke', () => {
it.skipIf(process.platform === 'win32')('spawns the shell as a login shell (loads ~/.profile)', async () => {
const home = mkdtempSync(join(tmpdir(), 'dsh-sidebar-login-'))
const previousHome = process.env.HOME
let handle: SidebarPty | undefined
try {
// A login bash reads ~/.profile (a non-login interactive bash reads
// ~/.bashrc instead), so this marker proves the spawn used a login
@@ -199,7 +226,7 @@ describe('host plugin smoke', () => {
process.env.HOME = home
const manager = new PtyManager('/bin/bash', 3)
try {
const handle = manager.open('s5', 't1', process.cwd(), 80, 24)
handle = manager.open('s5', 't1', process.cwd(), 80, 24)
handle.pty.write('echo $DSH_LOGIN_MARKER\r')
const deadline = Date.now() + 5000
while (!handle.transcript.includes('loaded-from-profile') && Date.now() < deadline) {
@@ -212,7 +239,7 @@ describe('host plugin smoke', () => {
} finally {
if (previousHome === undefined) delete process.env.HOME
else process.env.HOME = previousHome
rmSync(home, { recursive: true, force: true })
await rmTempDirAfterPtyExit(handle ?? { exited: true }, home)
}
})
+3
View File
@@ -1102,6 +1102,9 @@ describe('editor language mapping', () => {
expect(languageKeyForExt('yaml')).toBe('yaml')
expect(languageKeyForExt('sh')).toBe('shell')
expect(languageKeyForExt('md')).toBe('md')
expect(languageKeyForExt('cs')).toBe('csharp')
expect(languageKeyForExt('kt')).toBe('kotlin')
expect(languageKeyForExt('swift')).toBe('swift')
expect(languageKeyForExt('txt')).toBeNull()
expect(languageKeyForExt('log')).toBeNull()
expect(languageKeyForExt('')).toBeNull()