Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try out new find files API #6104

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"contribShareMenu",
"diffCommand",
"fileComments",
"findFiles2New",
"findTextInFilesNew",
"quickDiffProvider",
"shareProvider",
"tokenInformation",
Expand Down
79 changes: 79 additions & 0 deletions src/@types/vscode.proposed.findFiles2New.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

declare module 'vscode' {

export interface FindFiles2OptionsNew {
/**
* A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace.
*/
exclude?: GlobPattern[];

/**
* Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}.
*/
useExcludeSettings?: ExcludeSettingOptions;

/**
* The maximum number of results to search for
*/
maxResults?: number;

/**
* Which file locations we should look for ignore (.gitignore or .ignore) files to respect.
*
* When any of these fields are `undefined`, we will:
* - assume the value if possible (e.g. if only one is valid)
* or
* - follow settings using the value for the corresponding `search.use*IgnoreFiles` settting.
*
* Will log an error if an invalid combination is set.
*/
useIgnoreFiles?: {
/**
* Use ignore files at the current workspace root.
* May default to `search.useIgnoreFiles` setting if not set.
*/
local?: boolean;
/**
* Use ignore files at the parent directory. When set to `true`, {@link FindFiles2OptionsNew.useIgnoreFiles.local} must also be `true`.
* May default to `search.useParentIgnoreFiles` setting if not set.
*/
parent?: boolean;
/**
* Use global ignore files. When set to `true`, {@link FindFiles2OptionsNew.useIgnoreFiles.local} must also be `true`.
* May default to `search.useGlobalIgnoreFiles` setting if not set.
*/
global?: boolean;
};

/**
* Whether symlinks should be followed while searching.
* Defaults to the value for `search.followSymlinks` in settings.
* For more info, see the setting description for `search.followSymlinks`.
*/
followSymlinks?: boolean;
}

export namespace workspace {
/**
* WARNING: VERY EXPERIMENTAL.
*
* Find files across all {@link workspace.workspaceFolders workspace folders} in the workspace.
*
* @example
*
* @param filePattern A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace. Use a {@link RelativePattern relative pattern}
* to restrict the search results to a {@link WorkspaceFolder workspace folder}.
* @param options A set of {@link FindFiles2Options FindFiles2Options} that defines where and how to search (e.g. exclude settings).
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @returns A thenable that resolves to an array of resource identifiers. Will return no results if no
* {@link workspace.workspaceFolders workspace folders} are opened.
*/
export function findFiles2New(filePattern: GlobPattern[], options?: FindFiles2OptionsNew, token?: CancellationToken): Thenable<Uri[]>;
}
}
143 changes: 143 additions & 0 deletions src/@types/vscode.proposed.findTextInFilesNew.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

declare module 'vscode' {

// https://github.com/microsoft/vscode/issues/59924

export interface FindTextInFilesOptionsNew {
/**
* A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern
* will be matched against the file paths of files relative to their workspace. Use a {@link RelativePattern relative pattern}
* to restrict the search results to a {@link WorkspaceFolder workspace folder}.
*/
include?: GlobPattern[];

/**
* A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern
* will be matched against the file paths of resulting matches relative to their workspace.
*/
exclude?: GlobPattern[];

/**
* Which settings to follow when searching for files. Defaults to {@link ExcludeSettingOptions.searchAndFilesExclude}.
*/
useExcludeSettings?: ExcludeSettingOptions;

/**
* The maximum number of results to search for
*/
maxResults?: number;


/**
* Which file locations we should look for ignore (.gitignore or .ignore) files to respect.
*
* When any of these fields are `undefined`, we will:
* - assume the value if possible (e.g. if only one is valid)
* or
* - follow settings using the value for the corresponding `search.use*IgnoreFiles` settting.
*
* Will log an error if an invalid combination is set.
*/
useIgnoreFiles?: {
/**
* Use ignore files at the current workspace root.
* May default to `search.useIgnoreFiles` setting if not set.
*/
local?: boolean;
/**
* Use ignore files at the parent directory. When set to `true`, {@link FindTextInFilesOptionsNew.useIgnoreFiles.local} must be `true`.
* May default to `search.useParentIgnoreFiles` setting if not set.
*/
parent?: boolean;
/**
* Use global ignore files. When set to `true`, {@link FindTextInFilesOptionsNew.useIgnoreFiles.local} must also be `true`.
* May default to `search.useGlobalIgnoreFiles` setting if not set.
*/
global?: boolean;
};

/**
* Whether symlinks should be followed while searching.
* Defaults to the value for `search.followSymlinks` in settings.
* For more info, see the setting description for `search.followSymlinks`.
*/
followSymlinks?: boolean;

/**
* Interpret files using this encoding.
* See the vscode setting `"files.encoding"`
*/
encoding?: string;

/**
* Options to specify the size of the result text preview.
*/
previewOptions?: {
/**
* The maximum number of lines in the preview.
* Only search providers that support multiline search will ever return more than one line in the match.
*/
matchLines?: number;

/**
* The maximum number of characters included per line.
*/
charsPerLine?: number;
};

/**
* Number of lines of context to include before and after each match.
*/
surroundingContext?: number;
}

export interface FindTextInFilesResponse {
/**
* The results of the text search, in batches. To get completion information, wait on the `complete` property.
*/
results: AsyncIterable<TextSearchResultNew>;
/**
* The text search completion information. This resolves on completion.
*/
complete: Thenable<TextSearchCompleteNew>;
}

/*
* Options for following search.exclude and files.exclude settings.
*/
export enum ExcludeSettingOptions {
/*
* Don't use any exclude settings.
*/
none = 1,
/*
* Use:
* - files.exclude setting
*/
filesExclude = 2,
/*
* Use:
* - files.exclude setting
* - search.exclude setting
*/
searchAndFilesExclude = 3
}

export namespace workspace {
/**
* WARNING: VERY EXPERIMENTAL.
*
* Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace.
* @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words.
* @param options An optional set of query options. Include and exclude patterns, maxResults, etc.
* @param callback A callback, called for each result
* @param token A token that can be used to signal cancellation to the underlying search engine.
* @return A thenable that resolves when the search is complete.
*/
export function findTextInFilesNew(query: TextSearchQueryNew, options?: FindTextInFilesOptionsNew, token?: CancellationToken): FindTextInFilesResponse;
}
}
71 changes: 43 additions & 28 deletions src/github/folderRepositoryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ enum PagedDataType {
IssueSearch,
}

const CACHED_TEMPLATE_BODY = 'templateBody';
const CACHED_TEMPLATE_URI = 'templateUri';

export class FolderRepositoryManager implements vscode.Disposable {
static ID = 'FolderRepositoryManager';
Expand Down Expand Up @@ -1211,50 +1211,53 @@ export class FolderRepositoryManager implements vscode.Disposable {

async getIssueTemplates(): Promise<vscode.Uri[]> {
const pattern = '{docs,.github}/ISSUE_TEMPLATE/*.md';
return vscode.workspace.findFiles(
new vscode.RelativePattern(this._repository.rootUri, pattern), null
return vscode.workspace.findFiles2New(
[new vscode.RelativePattern(this._repository.rootUri, pattern)], { useExcludeSettings: vscode.ExcludeSettingOptions.filesExclude }
);
}

async getPullRequestTemplateBody(owner: string): Promise<string | undefined> {
try {
const template = await this.getPullRequestTemplateWithCache(owner);
if (template) {
return template;
}
// First try for a local template
const templateUri = await this.getPullRequestTemplatesWithCache();

// If there's no local template, look for a owner-wide template
return this.getOwnerPullRequestTemplate(owner);
} catch (e) {
Logger.error(`Error fetching pull request template for ${owner}: ${e instanceof Error ? e.message : e}`, this.id);
if (templateUri) {
try {
const templateContent = await vscode.workspace.fs.readFile(templateUri);
return new TextDecoder('utf-8').decode(templateContent);
} catch (e) {
Logger.warn(`Reading pull request template failed: ${e}`);
}
}

// If there's no local template, look for a owner-wide template
return this.getOwnerPullRequestTemplates(owner);
}

private async getPullRequestTemplateWithCache(owner: string): Promise<string | undefined> {
const cacheLocation = `${CACHED_TEMPLATE_BODY}+${this.repository.rootUri.toString()}`;
async getPullRequestTemplatesWithCache(): Promise<vscode.Uri | undefined> {
const cacheLocation = `${CACHED_TEMPLATE_URI}+${this.repository.rootUri.toString()}`;

const findTemplate = this.getPullRequestTemplate(owner).then((template) => {
const findTemplate = this.getFirstLocalPullRequestTemplate().then((template) => {
//update cache
if (template) {
this.context.workspaceState.update(cacheLocation, template);
this.context.workspaceState.update(cacheLocation, template.toString());
} else {
this.context.workspaceState.update(cacheLocation, null);
}
return template;
});
const hasCachedTemplate = this.context.workspaceState.keys().includes(cacheLocation);
const cachedTemplate = this.context.workspaceState.get<string | null>(cacheLocation);
const cachedTemplateLocation = this.context.workspaceState.get<string | null>(cacheLocation);
if (hasCachedTemplate) {
if (cachedTemplate === null) {
return undefined;
} else if (cachedTemplate) {
return cachedTemplate;
if (cachedTemplateLocation === null) {
return;
} else if (cachedTemplateLocation) {
return vscode.Uri.parse(cachedTemplateLocation);
}
}
return findTemplate;
}

private async getOwnerPullRequestTemplate(owner: string): Promise<string | undefined> {
private async getOwnerPullRequestTemplates(owner: string): Promise<string | undefined> {
const githubRepository = await this.createGitHubRepositoryFromOwnerName(owner, '.github');
if (!githubRepository) {
return undefined;
Expand All @@ -1265,13 +1268,25 @@ export class FolderRepositoryManager implements vscode.Disposable {
}
}

private async getPullRequestTemplate(owner: string): Promise<string | undefined> {
const repository = this.gitHubRepositories.find(repo => repo.remote.owner === owner);
if (!repository) {
return;
private async getFirstLocalPullRequestTemplate(): Promise<vscode.Uri | undefined> {
function patternToGlob(pattern: string) {
return new vscode.RelativePattern(this._repository.rootUri, pattern);
}
const templates = await repository.getPullRequestTemplates();
return templates ? templates[0] : undefined;

/**
* Places a PR template can be:
* - At the root, the docs folder, or the.github folder, named pull_request_template.md or PULL_REQUEST_TEMPLATE.md
* - At the same folder locations under a PULL_REQUEST_TEMPLATE folder with any name
*/
const pattern1 = patternToGlob('{pull_request_template,PULL_REQUEST_TEMPLATE}.{md,txt}');
const pattern2 = patternToGlob('{docs,.github}/{pull_request_template,PULL_REQUEST_TEMPLATE}.{md,txt}');
const pattern3 = patternToGlob('{pull_request_template,PULL_REQUEST_TEMPLATE}');
const pattern4 = patternToGlob('{docs,.github}/{pull_request_template,PULL_REQUEST_TEMPLATE}');
const pattern5 = patternToGlob('PULL_REQUEST_TEMPLATE/*.md');
const pattern6 = patternToGlob('{docs,.github}/PULL_REQUEST_TEMPLATE/*.md');

const result = await vscode.workspace.findFiles2New([pattern1, pattern2, pattern3, pattern4, pattern5, pattern6], { maxResults: 1, useExcludeSettings: vscode.ExcludeSettingOptions.filesExclude });
return result.length > 0 ? result[0] : undefined;
}

async getPullRequestDefaults(branch?: Branch): Promise<PullRequestDefaults> {
Expand Down