openclaw/extensions/diffs/src/language-hints.ts

117 lines
3.3 KiB
TypeScript

import { resolveLanguage } from "@pierre/diffs";
import type { FileContents, FileDiffMetadata, SupportedLanguages } from "@pierre/diffs";
import type { DiffViewerPayload } from "./types.js";
const PASSTHROUGH_LANGUAGE_HINTS = new Set<SupportedLanguages>(["ansi", "text"]);
type DiffPayloadFile = FileContents | FileDiffMetadata;
export async function normalizeSupportedLanguageHint(
value?: string,
): Promise<SupportedLanguages | undefined> {
const normalized = value?.trim();
if (!normalized) {
return undefined;
}
if (PASSTHROUGH_LANGUAGE_HINTS.has(normalized as SupportedLanguages)) {
return normalized as SupportedLanguages;
}
try {
await resolveLanguage(normalized as Exclude<SupportedLanguages, "text" | "ansi">);
return normalized as SupportedLanguages;
} catch {
return undefined;
}
}
export async function filterSupportedLanguageHints(
values: Iterable<string>,
): Promise<SupportedLanguages[]> {
return normalizeSupportedLanguageHints(values, { fallbackToText: true });
}
async function normalizeSupportedLanguageHints(
values: Iterable<string>,
options: { fallbackToText: boolean },
): Promise<SupportedLanguages[]> {
const supported = new Set<SupportedLanguages>();
for (const value of values) {
const normalized = await normalizeSupportedLanguageHint(value);
if (!normalized) {
continue;
}
supported.add(normalized);
}
if (options.fallbackToText && supported.size === 0) {
supported.add("text");
}
return [...supported];
}
export function collectDiffPayloadLanguageHints(payload: {
fileDiff?: FileDiffMetadata;
oldFile?: FileContents;
newFile?: FileContents;
}): SupportedLanguages[] {
const langs = new Set<SupportedLanguages>();
if (payload.fileDiff?.lang) {
langs.add(payload.fileDiff.lang);
}
if (payload.oldFile?.lang) {
langs.add(payload.oldFile.lang);
}
if (payload.newFile?.lang) {
langs.add(payload.newFile.lang);
}
return [...langs];
}
async function normalizeDiffPayloadFileLanguage(
file: DiffPayloadFile | undefined,
): Promise<DiffPayloadFile | undefined> {
if (!file) {
return undefined;
}
if (typeof file.lang !== "string") {
return file;
}
const normalized = await normalizeSupportedLanguageHint(file.lang);
if (file.lang === normalized) {
return file;
}
if (!normalized) {
return {
...file,
lang: "text",
};
}
return {
...file,
lang: normalized,
};
}
export async function normalizeDiffViewerPayloadLanguages(
payload: DiffViewerPayload,
): Promise<DiffViewerPayload> {
const [fileDiff, oldFile, newFile, payloadLangs] = await Promise.all([
normalizeDiffPayloadFileLanguage(payload.fileDiff) as Promise<FileDiffMetadata | undefined>,
normalizeDiffPayloadFileLanguage(payload.oldFile) as Promise<FileContents | undefined>,
normalizeDiffPayloadFileLanguage(payload.newFile) as Promise<FileContents | undefined>,
normalizeSupportedLanguageHints(payload.langs, { fallbackToText: false }),
]);
const langs = new Set<SupportedLanguages>(payloadLangs);
for (const lang of collectDiffPayloadLanguageHints({ fileDiff, oldFile, newFile })) {
langs.add(lang);
}
if (langs.size === 0) {
langs.add("text");
}
return {
...payload,
fileDiff,
oldFile,
newFile,
langs: [...langs],
};
}