fix(coding-agent): disambiguate compact extension labels

closes #3308
This commit is contained in:
Armin Ronacher
2026-04-17 09:46:47 +02:00
parent ddbf642179
commit 32a305cb9b
3 changed files with 359 additions and 15 deletions

View File

@@ -906,6 +906,20 @@ export class InteractiveMode {
* Get a short path relative to the package root for display.
*/
private getShortPath(fullPath: string, sourceInfo?: SourceInfo): string {
const baseDir = sourceInfo?.baseDir;
if (baseDir && this.isPackageSource(sourceInfo)) {
const relativePath = path.relative(path.resolve(baseDir), path.resolve(fullPath));
if (
relativePath &&
relativePath !== "." &&
!relativePath.startsWith("..") &&
!relativePath.startsWith(`..${path.sep}`) &&
!path.isAbsolute(relativePath)
) {
return relativePath.replace(/\\/g, "/");
}
}
const source = sourceInfo?.source ?? "";
const npmMatch = fullPath.match(/node_modules\/(@?[^/]+(?:\/[^/]+)?)\/(.*)/);
if (npmMatch && source.startsWith("npm:")) {
@@ -920,6 +934,108 @@ export class InteractiveMode {
return this.formatDisplayPath(fullPath);
}
private getCompactPathLabel(resourcePath: string, sourceInfo?: SourceInfo): string {
const shortPath = this.getShortPath(resourcePath, sourceInfo);
const normalizedPath = shortPath.replace(/\\/g, "/");
const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~");
if (segments.length > 0) {
return segments[segments.length - 1]!;
}
return shortPath;
}
private getCompactPackageSourceLabel(sourceInfo?: SourceInfo): string {
const source = sourceInfo?.source ?? "";
if (source.startsWith("npm:")) {
return source.slice("npm:".length) || source;
}
const gitSource = parseGitUrl(source);
if (gitSource) {
return gitSource.path || source;
}
return source;
}
private getCompactExtensionLabel(resourcePath: string, sourceInfo?: SourceInfo): string {
if (!this.isPackageSource(sourceInfo)) {
return this.getCompactPathLabel(resourcePath, sourceInfo);
}
const sourceLabel = this.getCompactPackageSourceLabel(sourceInfo);
if (!sourceLabel) {
return this.getCompactPathLabel(resourcePath, sourceInfo);
}
const shortPath = this.getShortPath(resourcePath, sourceInfo).replace(/\\/g, "/");
const packagePath = shortPath.startsWith("extensions/") ? shortPath.slice("extensions/".length) : shortPath;
const parsedPath = path.posix.parse(packagePath);
if (parsedPath.name === "index") {
return !parsedPath.dir || parsedPath.dir === "." ? sourceLabel : `${sourceLabel}:${parsedPath.dir}`;
}
return `${sourceLabel}:${packagePath}`;
}
private getCompactDisplayPathSegments(resourcePath: string): string[] {
return this.formatDisplayPath(resourcePath)
.replace(/\\/g, "/")
.split("/")
.filter((segment) => segment.length > 0 && segment !== "~");
}
private getCompactNonPackageExtensionLabel(
resourcePath: string,
index: number,
allPaths: Array<{ path: string; segments: string[] }>,
): string {
const segments = allPaths[index]?.segments;
if (!segments || segments.length === 0) {
return this.getCompactPathLabel(resourcePath);
}
for (let segmentCount = 1; segmentCount <= segments.length; segmentCount += 1) {
const candidate = segments.slice(-segmentCount).join("/");
const isUnique = allPaths.every((item, itemIndex) => {
if (itemIndex === index) {
return true;
}
return item.segments.slice(-segmentCount).join("/") !== candidate;
});
if (isUnique) {
return candidate;
}
}
return segments.join("/");
}
private getCompactExtensionLabels(extensions: Array<{ path: string; sourceInfo?: SourceInfo }>): string[] {
const nonPackageExtensions = extensions
.map((extension) => ({
path: extension.path,
sourceInfo: extension.sourceInfo,
segments: this.getCompactDisplayPathSegments(extension.path),
}))
.filter((extension) => !this.isPackageSource(extension.sourceInfo));
return extensions.map((extension) => {
if (this.isPackageSource(extension.sourceInfo)) {
return this.getCompactExtensionLabel(extension.path, extension.sourceInfo);
}
const nonPackageIndex = nonPackageExtensions.findIndex((item) => item.path === extension.path);
if (nonPackageIndex === -1) {
return this.getCompactPathLabel(extension.path, extension.sourceInfo);
}
return this.getCompactNonPackageExtensionLabel(extension.path, nonPackageIndex, nonPackageExtensions);
});
}
private getDisplaySourceInfo(sourceInfo?: SourceInfo): {
label: string;
scopeLabel?: string;
@@ -1123,15 +1239,6 @@ export class InteractiveMode {
}
const sectionHeader = (name: string, color: ThemeColor = "mdHeading") => theme.fg(color, `[${name}]`);
const compactPath = (resourcePath: string, sourceInfo?: SourceInfo): string => {
const shortPath = this.getShortPath(resourcePath, sourceInfo);
const normalizedPath = shortPath.replace(/\\/g, "/");
const segments = normalizedPath.split("/").filter((segment) => segment.length > 0 && segment !== "~");
if (segments.length > 0) {
return segments[segments.length - 1]!;
}
return shortPath;
};
const formatCompactList = (items: string[], options?: { sort?: boolean }): string => {
const labels = items.map((item) => item.trim()).filter((item) => item.length > 0);
if (options?.sort !== false) {
@@ -1240,9 +1347,7 @@ export class InteractiveMode {
formatPath: (item) => this.formatDisplayPath(item.path),
formatPackagePath: (item) => this.getShortPath(item.path, item.sourceInfo),
});
const extensionCompactList = formatCompactList(
extensions.map((extension) => compactPath(extension.path, extension.sourceInfo)),
);
const extensionCompactList = formatCompactList(this.getCompactExtensionLabels(extensions));
addLoadedSection("Extensions", extensionCompactList, extList, "mdHeading");
}
@@ -1262,7 +1367,8 @@ export class InteractiveMode {
});
const themeCompactList = formatCompactList(
customThemes.map(
(loadedTheme) => loadedTheme.name ?? compactPath(loadedTheme.sourcePath!, loadedTheme.sourceInfo),
(loadedTheme) =>
loadedTheme.name ?? this.getCompactPathLabel(loadedTheme.sourcePath!, loadedTheme.sourceInfo),
),
);
addLoadedSection("Themes", themeCompactList, themeList);