Path: blob/main/extensions/copilot/src/extension/prompt/common/fileTreeParser.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type * as vscode from 'vscode';6import { URI } from '../../../util/vs/base/common/uri';7import { ChatResponseFileTreePart } from '../../../vscodeTypes';89/**10* Converts a markdown-style file tree into a ChatResponseFileTreePart.11* @param fileStructure Markdown-style file tree12* @param generatePreviewURI Factory that converts a filename to a preview URI13*/14export function convertFileTreeToChatResponseFileTree(15fileStructure: string,16generatePreviewURI: (filename: string) => URI,17): { chatResponseTree: ChatResponseFileTreePart; projectName: string } {18const lines = fileStructure.trim().split('\n');19const fileTree: vscode.ChatResponseFileTree[] = [];2021let baseUri: URI | undefined;22const root: vscode.ChatResponseFileTree = { name: '', children: [] };23fileTree[0] = root;2425for (const line of lines) {26let depth = calculateDepth(line);27const index = line.lastIndexOf('── ');28const name = index >= 0 ? line.substring(index + 3) : line;2930const fileNode: vscode.ChatResponseFileTree = { name };3132if (depth === 0) {33baseUri = generatePreviewURI(name);34root.name = name;35continue;36}37else {38while (depth > 0 && fileTree[depth - 1] === undefined) {39depth--;40}41if (fileTree[depth - 1].children === undefined) {42fileTree[depth - 1].children = [fileNode];43} else {44fileTree[depth - 1].children?.push(fileNode);45}46fileTree[depth] = fileNode;47}48}49if (baseUri === undefined) {50throw new Error('Base URI is undefined');51}52const filteredTree = filterChatResponseFileTree(root.children!);53root.children = filteredTree.sort((a, b) => (a.children && !b.children) ? -1 : 1);54return {55chatResponseTree: new ChatResponseFileTreePart([root], baseUri),56projectName: root.name57};58}5960/**61* List filenames in the tree, separated by forward-slashes.62*/63export function listFilesInResponseFileTree(tree: vscode.ChatResponseFileTree[]): string[] {64const queue = tree.map(node => ({ node, path: node.name }));65const result: string[] = [];6667while (queue.length > 0) {68const { node, path } = queue.shift()!;69if (node.children && node.children.length > 0) {70for (const child of node.children) {71queue.push({ node: child, path: `${path}/${child.name}` });72}73} else {74result.push(path);75}76}7778return result;79}8081function calculateDepth(inputString: string): number {82let depth = (inputString.match(/│ /g) || []).length;83depth += (inputString.match(/\| /g) || []).length;84depth += (inputString.match(/ /g) || []).length;85depth += (inputString.match(/├── /g) || []).length;86depth += (inputString.match(/└── /g) || []).length;8788return depth;89}9091const filterList = [92/* compile/runtime files */ 'node_modules', 'out', 'bin', 'debug', 'obj', 'lib', '.dll', '.pdb', '.lib',93/* image assets */ '.jpg', '.png', '.ico', '.gif', '.svg', '.jpeg', '.tiff', '.bmp', '.webp', '.jpeg',94/* other files we should not be included in a new project */'.gitignore', 'LICENSE.txt', 'yarn.lock', 'package-lock.json'95];9697function filterChatResponseFileTree(fileTree: vscode.ChatResponseFileTree[]): vscode.ChatResponseFileTree[] {98const filteredTree: vscode.ChatResponseFileTree[] = [];99100for (const node of fileTree) {101102if (!isNodeInFilterList(node)) {103if (node.children) {104node.children = filterChatResponseFileTree(node.children);105}106filteredTree.push(node);107}108}109110return filteredTree;111}112113function isNodeInFilterList(node: vscode.ChatResponseFileTree): boolean {114if (filterList.includes(node.name)) {115return true;116}117118return false;119}120121122