Browse Source

Renames SearchPattern to SearchQuery

Renames more to hasMore
main
Eric Amodio 2 years ago
parent
commit
954a3be68d
19 changed files with 244 additions and 222 deletions
  1. +17
    -17
      src/commands/git/search.ts
  2. +2
    -2
      src/commands/searchCommits.ts
  3. +2
    -2
      src/commands/showCommitsInView.ts
  4. +19
    -96
      src/env/node/git/localGitProvider.ts
  5. +3
    -3
      src/git/gitProvider.ts
  6. +3
    -3
      src/git/gitProviderService.ts
  7. +1
    -1
      src/git/models/graph.ts
  8. +3
    -3
      src/git/models/repository.ts
  9. +116
    -16
      src/git/search.ts
  10. +9
    -9
      src/plus/github/githubGitProvider.ts
  11. +8
    -15
      src/plus/webviews/graph/graphWebview.ts
  12. +6
    -11
      src/plus/webviews/graph/protocol.ts
  13. +2
    -2
      src/storage.ts
  14. +11
    -11
      src/views/nodes/searchResultsNode.ts
  15. +12
    -3
      src/views/searchAndCompareView.ts
  16. +18
    -18
      src/webviews/apps/plus/graph/GraphWrapper.tsx
  17. +5
    -5
      src/webviews/apps/plus/graph/graph.tsx
  18. +5
    -3
      src/webviews/apps/shared/components/search/search-field.ts
  19. +2
    -2
      src/webviews/rebase/rebaseEditor.ts

+ 17
- 17
src/commands/git/search.ts View File

@ -5,8 +5,8 @@ import { getContext } from '../../context';
import type { GitCommit } from '../../git/models/commit'; import type { GitCommit } from '../../git/models/commit';
import type { GitLog } from '../../git/models/log'; import type { GitLog } from '../../git/models/log';
import type { Repository } from '../../git/models/repository'; import type { Repository } from '../../git/models/repository';
import type { SearchOperators, SearchPattern } from '../../git/search';
import { getSearchPatternComparisonKey, parseSearchOperations, searchOperators } from '../../git/search';
import type { SearchOperators, SearchQuery } from '../../git/search';
import { getSearchQueryComparisonKey, parseSearchQuery, searchOperators } from '../../git/search';
import type { QuickPickItemOfT } from '../../quickpicks/items/common'; import type { QuickPickItemOfT } from '../../quickpicks/items/common';
import { ActionQuickPickItem } from '../../quickpicks/items/common'; import { ActionQuickPickItem } from '../../quickpicks/items/common';
import { pluralize } from '../../system/string'; import { pluralize } from '../../system/string';
@ -34,7 +34,7 @@ interface Context {
title: string; title: string;
} }
interface State extends Required<SearchPattern> {
interface State extends Required<SearchQuery> {
repo: string | Repository; repo: string | Repository;
openPickInView?: boolean; openPickInView?: boolean;
showResultsInSideBar: boolean | SearchResultsNode; showResultsInSideBar: boolean | SearchResultsNode;
@ -73,7 +73,7 @@ export class SearchGitCommand extends QuickCommand {
counter++; counter++;
} }
if (args?.state?.pattern != null && !args.prefillOnly) {
if (args?.state?.query != null && !args.prefillOnly) {
counter++; counter++;
} }
@ -144,7 +144,7 @@ export class SearchGitCommand extends QuickCommand {
} }
} }
if (state.counter < 2 || state.pattern == null) {
if (state.counter < 2 || state.query == null) {
const result = yield* this.pickSearchOperatorStep(state as SearchStepState, context); const result = yield* this.pickSearchOperatorStep(state as SearchStepState, context);
if (result === StepResult.Break) { if (result === StepResult.Break) {
// If we skipped the previous step, make sure we back up past it // If we skipped the previous step, make sure we back up past it
@ -152,21 +152,21 @@ export class SearchGitCommand extends QuickCommand {
state.counter--; state.counter--;
} }
state.pattern = undefined;
state.query = undefined;
continue; continue;
} }
state.pattern = result;
state.query = result;
} }
const search: SearchPattern = {
pattern: state.pattern,
const search: SearchQuery = {
query: state.query,
matchAll: state.matchAll, matchAll: state.matchAll,
matchCase: state.matchCase, matchCase: state.matchCase,
matchRegex: state.matchRegex, matchRegex: state.matchRegex,
}; };
const searchKey = getSearchPatternComparisonKey(search);
const searchKey = getSearchQueryComparisonKey(search);
if (context.resultsPromise == null || context.resultsKey !== searchKey) { if (context.resultsPromise == null || context.resultsKey !== searchKey) {
context.resultsPromise = state.repo.searchForCommits(search); context.resultsPromise = state.repo.searchForCommits(search);
@ -178,7 +178,7 @@ export class SearchGitCommand extends QuickCommand {
state.repo.path, state.repo.path,
search, search,
{ {
label: { label: `for ${state.pattern}` },
label: { label: `for ${state.query}` },
}, },
context.resultsPromise, context.resultsPromise,
state.showResultsInSideBar instanceof SearchResultsNode ? state.showResultsInSideBar : undefined, state.showResultsInSideBar instanceof SearchResultsNode ? state.showResultsInSideBar : undefined,
@ -195,10 +195,10 @@ export class SearchGitCommand extends QuickCommand {
onDidLoadMore: log => (context.resultsPromise = Promise.resolve(log)), onDidLoadMore: log => (context.resultsPromise = Promise.resolve(log)),
placeholder: (context, log) => placeholder: (context, log) =>
log == null log == null
? `No results for ${state.pattern}`
? `No results for ${state.query}`
: `${pluralize('result', log.count, { : `${pluralize('result', log.count, {
format: c => (log.hasMore ? `${c}+` : undefined), format: c => (log.hasMore ? `${c}+` : undefined),
})} for ${state.pattern}`,
})} for ${state.query}`,
picked: context.commit?.ref, picked: context.commit?.ref,
showInSideBarCommand: new ActionQuickPickItem( showInSideBarCommand: new ActionQuickPickItem(
'$(link-external) Show Results in Side Bar', '$(link-external) Show Results in Side Bar',
@ -207,7 +207,7 @@ export class SearchGitCommand extends QuickCommand {
repoPath, repoPath,
search, search,
{ {
label: { label: `for ${state.pattern}` },
label: { label: `for ${state.query}` },
reveal: { reveal: {
select: true, select: true,
focus: false, focus: false,
@ -224,7 +224,7 @@ export class SearchGitCommand extends QuickCommand {
repoPath, repoPath,
search, search,
{ {
label: { label: `for ${state.pattern}` },
label: { label: `for ${state.query}` },
reveal: { reveal: {
select: true, select: true,
focus: false, focus: false,
@ -317,7 +317,7 @@ export class SearchGitCommand extends QuickCommand {
matchOnDetail: true, matchOnDetail: true,
additionalButtons: [matchCaseButton, matchAllButton, matchRegexButton], additionalButtons: [matchCaseButton, matchAllButton, matchRegexButton],
items: items, items: items,
value: state.pattern,
value: state.query,
selectValueWhenShown: false, selectValueWhenShown: false,
onDidAccept: (quickpick): boolean => { onDidAccept: (quickpick): boolean => {
const pick = quickpick.selectedItems[0]; const pick = quickpick.selectedItems[0];
@ -351,7 +351,7 @@ export class SearchGitCommand extends QuickCommand {
// Simulate an extra step if we have a value // Simulate an extra step if we have a value
state.counter = value ? 3 : 2; state.counter = value ? 3 : 2;
const operations = parseSearchOperations(value);
const operations = parseSearchQuery(value);
quickpick.title = appendReposToTitle( quickpick.title = appendReposToTitle(
operations.size === 0 || operations.size > 1 operations.size === 0 || operations.size > 1

+ 2
- 2
src/commands/searchCommits.ts View File

@ -2,14 +2,14 @@ import { executeGitCommand } from '../commands/gitCommands.actions';
import { configuration } from '../configuration'; import { configuration } from '../configuration';
import { Commands } from '../constants'; import { Commands } from '../constants';
import type { Container } from '../container'; import type { Container } from '../container';
import type { SearchPattern } from '../git/search';
import type { SearchQuery } from '../git/search';
import { command } from '../system/command'; import { command } from '../system/command';
import { SearchResultsNode } from '../views/nodes/searchResultsNode'; import { SearchResultsNode } from '../views/nodes/searchResultsNode';
import type { CommandContext } from './base'; import type { CommandContext } from './base';
import { Command, isCommandContextViewNodeHasRepository } from './base'; import { Command, isCommandContextViewNodeHasRepository } from './base';
export interface SearchCommitsCommandArgs { export interface SearchCommitsCommandArgs {
search?: Partial<SearchPattern>;
search?: Partial<SearchQuery>;
repoPath?: string; repoPath?: string;
prefillOnly?: boolean; prefillOnly?: boolean;

+ 2
- 2
src/commands/showCommitsInView.ts View File

@ -3,7 +3,7 @@ import { executeGitCommand } from '../commands/gitCommands.actions';
import { Commands } from '../constants'; import { Commands } from '../constants';
import type { Container } from '../container'; import type { Container } from '../container';
import { GitUri } from '../git/gitUri'; import { GitUri } from '../git/gitUri';
import { getSearchPatternFromCommits } from '../git/search';
import { createSearchQueryForCommits } from '../git/search';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { showFileNotUnderSourceControlWarningMessage, showGenericErrorMessage } from '../messages'; import { showFileNotUnderSourceControlWarningMessage, showGenericErrorMessage } from '../messages';
import { command } from '../system/command'; import { command } from '../system/command';
@ -88,7 +88,7 @@ export class ShowCommitsInViewCommand extends ActiveEditorCommand {
command: 'search', command: 'search',
state: { state: {
repo: args?.repoPath, repo: args?.repoPath,
pattern: getSearchPatternFromCommits(args.refs),
query: createSearchQueryForCommits(args.refs),
showResultsInSideBar: true, showResultsInSideBar: true,
}, },
}); });

+ 19
- 96
src/env/node/git/localGitProvider.ts View File

@ -106,8 +106,8 @@ import type { RemoteProvider } from '../../../git/remotes/remoteProvider';
import type { RemoteProviders } from '../../../git/remotes/remoteProviders'; import type { RemoteProviders } from '../../../git/remotes/remoteProviders';
import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/remoteProviders'; import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/remoteProviders';
import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider'; import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider';
import type { GitSearch, SearchPattern } from '../../../git/search';
import { getSearchPatternComparisonKey, parseSearchOperations } from '../../../git/search';
import type { GitSearch, SearchQuery } from '../../../git/search';
import { getGitArgsFromSearchQuery, getSearchQueryComparisonKey } from '../../../git/search';
import { Logger } from '../../../logger'; import { Logger } from '../../../logger';
import type { LogScope } from '../../../logger'; import type { LogScope } from '../../../logger';
import { import {
@ -155,7 +155,6 @@ const RepoSearchWarnings = {
doesNotExist: /no such file or directory/i, doesNotExist: /no such file or directory/i,
}; };
const doubleQuoteRegex = /"/g;
const driveLetterRegex = /(?<=^\/?)([a-zA-Z])(?=:\/)/; const driveLetterRegex = /(?<=^\/?)([a-zA-Z])(?=:\/)/;
const userConfigRegex = /^user\.(name|email) (.*)$/gm; const userConfigRegex = /^user\.(name|email) (.*)$/gm;
const mappedAuthorRegex = /(.+)\s<(.+)>/; const mappedAuthorRegex = /(.+)\s<(.+)>/;
@ -1859,7 +1858,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
paging: { paging: {
limit: limit === 0 ? count : limit, limit: limit === 0 ? count : limit,
startingCursor: startingCursor, startingCursor: startingCursor,
more: count > limit,
hasMore: count > limit,
}, },
more: async (limit: number, sha?: string): Promise<GitGraph | undefined> => more: async (limit: number, sha?: string): Promise<GitGraph | undefined> =>
getCommitsForGraphCore.call(this, limit, sha, cursor), getCommitsForGraphCore.call(this, limit, sha, cursor),
@ -2640,20 +2639,20 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log() @log()
async searchForCommitsSimple( async searchForCommitsSimple(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> { ): Promise<GitSearch> {
search = { matchAll: false, matchCase: false, matchRegex: true, ...search }; search = { matchAll: false, matchCase: false, matchRegex: true, ...search };
const comparisonKey = getSearchPatternComparisonKey(search);
const comparisonKey = getSearchQueryComparisonKey(search);
try { try {
const { args: searchArgs, files, commits } = this.getArgsFromSearchPattern(search);
if (commits?.size) {
const { args: searchArgs, files, shas } = getGitArgsFromSearchQuery(search);
if (shas?.size) {
return { return {
repoPath: repoPath, repoPath: repoPath,
pattern: search,
query: search,
comparisonKey: comparisonKey, comparisonKey: comparisonKey,
results: commits,
results: shas,
}; };
} }
@ -2683,7 +2682,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (options?.cancellation?.isCancellationRequested) { if (options?.cancellation?.isCancellationRequested) {
// TODO@eamodio: Should we throw an error here? // TODO@eamodio: Should we throw an error here?
return { repoPath: repoPath, pattern: search, comparisonKey: comparisonKey, results: results };
return { repoPath: repoPath, query: search, comparisonKey: comparisonKey, results: results };
} }
const data = await this.git.log2( const data = await this.git.log2(
@ -2699,7 +2698,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
if (options?.cancellation?.isCancellationRequested) { if (options?.cancellation?.isCancellationRequested) {
// TODO@eamodio: Should we throw an error here? // TODO@eamodio: Should we throw an error here?
return { repoPath: repoPath, pattern: search, comparisonKey: comparisonKey, results: results };
return { repoPath: repoPath, query: search, comparisonKey: comparisonKey, results: results };
} }
let count = 0; let count = 0;
@ -2722,15 +2721,14 @@ export class LocalGitProvider implements GitProvider, Disposable {
return { return {
repoPath: repoPath, repoPath: repoPath,
pattern: search,
query: search,
comparisonKey: comparisonKey, comparisonKey: comparisonKey,
results: results, results: results,
paging: paging:
limit !== 0 && count > limit limit !== 0 && count > limit
? { ? {
limit: limit, limit: limit,
startingCursor: cursor?.sha,
more: true,
hasMore: true,
} }
: undefined, : undefined,
more: async (limit: number): Promise<GitSearch> => searchForCommitsCore.call(this, limit, cursor), more: async (limit: number): Promise<GitSearch> => searchForCommitsCore.call(this, limit, cursor),
@ -2740,10 +2738,10 @@ export class LocalGitProvider implements GitProvider, Disposable {
return searchForCommitsCore.call(this, limit); return searchForCommitsCore.call(this, limit);
} catch (ex) { } catch (ex) {
// TODO@eamodio: Should we throw an error here? // TODO@eamodio: Should we throw an error here?
// TODO@eamodio handle error reporting -- just invalid patterns? or more detailed?
// TODO@eamodio handle error reporting -- just invalid queries? or more detailed?
return { return {
repoPath: repoPath, repoPath: repoPath,
pattern: search,
query: search,
comparisonKey: comparisonKey, comparisonKey: comparisonKey,
results: new Set<string>(), results: new Set<string>(),
}; };
@ -2753,7 +2751,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
@log() @log()
async getLogForSearch( async getLogForSearch(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
): Promise<GitLog | undefined> { ): Promise<GitLog | undefined> {
search = { matchAll: false, matchCase: false, matchRegex: true, ...search }; search = { matchAll: false, matchCase: false, matchRegex: true, ...search };
@ -2762,7 +2760,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
const limit = options?.limit ?? configuration.get('advanced.maxSearchItems') ?? 0; const limit = options?.limit ?? configuration.get('advanced.maxSearchItems') ?? 0;
const similarityThreshold = configuration.get('advanced.similarityThreshold'); const similarityThreshold = configuration.get('advanced.similarityThreshold');
const { args, files, commits } = this.getArgsFromSearchPattern(search);
const { args, files, shas } = getGitArgsFromSearchQuery(search);
args.push(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`, '--'); args.push(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`, '--');
if (files.length !== 0) { if (files.length !== 0) {
@ -2773,7 +2771,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
ordering: configuration.get('advanced.commitOrdering'), ordering: configuration.get('advanced.commitOrdering'),
...options, ...options,
limit: limit, limit: limit,
useShow: Boolean(commits?.size),
useShow: Boolean(shas?.size),
}); });
const log = GitLogParser.parse( const log = GitLogParser.parse(
this.container, this.container,
@ -2802,84 +2800,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
} }
} }
private getArgsFromSearchPattern(search: SearchPattern): {
args: string[];
files: string[];
commits?: Set<string> | undefined;
} {
const operations = parseSearchOperations(search.pattern);
const searchArgs = new Set<string>();
const files: string[] = [];
let commits;
let op;
let values = operations.get('commit:');
if (values != null) {
// searchArgs.add('-m');
for (const value of values) {
searchArgs.add(value.replace(doubleQuoteRegex, ''));
}
commits = searchArgs;
} else {
searchArgs.add('--all');
searchArgs.add('--full-history');
searchArgs.add(search.matchRegex ? '--extended-regexp' : '--fixed-strings');
if (search.matchRegex && !search.matchCase) {
searchArgs.add('--regexp-ignore-case');
}
for ([op, values] of operations.entries()) {
switch (op) {
case 'message:':
searchArgs.add('-m');
if (search.matchAll) {
searchArgs.add('--all-match');
}
for (const value of values) {
searchArgs.add(`--grep=${value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '')}`);
}
break;
case 'author:':
searchArgs.add('-m');
for (const value of values) {
searchArgs.add(
`--author=${value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '')}`,
);
}
break;
case 'change:':
for (const value of values) {
searchArgs.add(
search.matchRegex
? `-G${value.replace(doubleQuoteRegex, '')}`
: `-S${value.replace(doubleQuoteRegex, '')}`,
);
}
break;
case 'file:':
for (const value of values) {
files.push(value.replace(doubleQuoteRegex, ''));
}
break;
}
}
}
return { args: [...searchArgs.values()], files: files, commits: commits };
}
private getLogForSearchMoreFn( private getLogForSearchMoreFn(
log: GitLog, log: GitLog,
search: SearchPattern,
search: SearchQuery,
options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null }, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null },
): (limit: number | undefined) => Promise<GitLog> { ): (limit: number | undefined) => Promise<GitLog> {
return async (limit: number | undefined) => { return async (limit: number | undefined) => {

+ 3
- 3
src/git/gitProvider.ts View File

@ -26,7 +26,7 @@ import type { GitWorktree } from './models/worktree';
import type { RemoteProvider } from './remotes/remoteProvider'; import type { RemoteProvider } from './remotes/remoteProvider';
import type { RemoteProviders } from './remotes/remoteProviders'; import type { RemoteProviders } from './remotes/remoteProviders';
import type { RichRemoteProvider } from './remotes/richRemoteProvider'; import type { RichRemoteProvider } from './remotes/richRemoteProvider';
import type { GitSearch, SearchPattern } from './search';
import type { GitSearch, SearchQuery } from './search';
export const enum GitProviderId { export const enum GitProviderId {
Git = 'git', Git = 'git',
@ -298,12 +298,12 @@ export interface GitProvider extends Disposable {
): Promise<Set<string> | undefined>; ): Promise<Set<string> | undefined>;
searchForCommitsSimple( searchForCommitsSimple(
repoPath: string | Uri, repoPath: string | Uri,
search: SearchPattern,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch>; ): Promise<GitSearch>;
getLogForSearch( getLogForSearch(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
options?: { options?: {
limit?: number | undefined; limit?: number | undefined;
ordering?: 'date' | 'author-date' | 'topo' | null | undefined; ordering?: 'date' | 'author-date' | 'topo' | null | undefined;

+ 3
- 3
src/git/gitProviderService.ts View File

@ -70,7 +70,7 @@ import type { GitWorktree } from './models/worktree';
import { RichRemoteProviders } from './remotes/remoteProviderConnections'; import { RichRemoteProviders } from './remotes/remoteProviderConnections';
import type { RemoteProviders } from './remotes/remoteProviders'; import type { RemoteProviders } from './remotes/remoteProviders';
import type { RichRemoteProvider } from './remotes/richRemoteProvider'; import type { RichRemoteProvider } from './remotes/richRemoteProvider';
import type { GitSearch, SearchPattern } from './search';
import type { GitSearch, SearchQuery } from './search';
const maxDefaultBranchWeight = 100; const maxDefaultBranchWeight = 100;
const weightedDefaultBranches = new Map<string, number>([ const weightedDefaultBranches = new Map<string, number>([
@ -1468,7 +1468,7 @@ export class GitProviderService implements Disposable {
@log() @log()
searchForCommitsSimple( searchForCommitsSimple(
repoPath: string | Uri, repoPath: string | Uri,
search: SearchPattern,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> { ): Promise<GitSearch> {
const { provider, path } = this.getProvider(repoPath); const { provider, path } = this.getProvider(repoPath);
@ -1478,7 +1478,7 @@ export class GitProviderService implements Disposable {
@log() @log()
async getLogForSearch( async getLogForSearch(
repoPath: string | Uri, repoPath: string | Uri,
search: SearchPattern,
search: SearchQuery,
options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
): Promise<GitLog | undefined> { ): Promise<GitLog | undefined> {
const { provider, path } = this.getProvider(repoPath); const { provider, path } = this.getProvider(repoPath);

+ 1
- 1
src/git/models/graph.ts View File

@ -32,7 +32,7 @@ export interface GitGraph {
readonly paging?: { readonly paging?: {
readonly limit: number | undefined; readonly limit: number | undefined;
readonly startingCursor: string | undefined; readonly startingCursor: string | undefined;
readonly more: boolean;
readonly hasMore: boolean;
}; };
more?(limit: number, sha?: string): Promise<GitGraph | undefined>; more?(limit: number, sha?: string): Promise<GitGraph | undefined>;

+ 3
- 3
src/git/models/repository.ts View File

@ -23,7 +23,7 @@ import type { GitProviderDescriptor } from '../gitProvider';
import { loadRemoteProviders } from '../remotes/remoteProviders'; import { loadRemoteProviders } from '../remotes/remoteProviders';
import type { RemoteProviders } from '../remotes/remoteProviders'; import type { RemoteProviders } from '../remotes/remoteProviders';
import type { RichRemoteProvider } from '../remotes/richRemoteProvider'; import type { RichRemoteProvider } from '../remotes/richRemoteProvider';
import type { GitSearch, SearchPattern } from '../search';
import type { GitSearch, SearchQuery } from '../search';
import type { BranchSortOptions, GitBranch } from './branch'; import type { BranchSortOptions, GitBranch } from './branch';
import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from './branch'; import { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from './branch';
import type { GitCommit } from './commit'; import type { GitCommit } from './commit';
@ -853,14 +853,14 @@ export class Repository implements Disposable {
} }
searchForCommits( searchForCommits(
search: SearchPattern,
search: SearchQuery,
options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo'; skip?: number }, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo'; skip?: number },
): Promise<GitLog | undefined> { ): Promise<GitLog | undefined> {
return this.container.git.getLogForSearch(this.path, search, options); return this.container.git.getLogForSearch(this.path, search, options);
} }
searchForCommitsSimple( searchForCommitsSimple(
search: SearchPattern,
search: SearchQuery,
options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> { ): Promise<GitSearch> {
return this.container.git.searchForCommitsSimple(this.path, search, options); return this.container.git.searchForCommitsSimple(this.path, search, options);

+ 116
- 16
src/git/search.ts View File

@ -28,7 +28,15 @@ export const searchOperators = new Set([
'change:', 'change:',
]); ]);
export interface SearchPattern {
export interface SearchQuery {
query: string;
matchAll?: boolean;
matchCase?: boolean;
matchRegex?: boolean;
}
// Don't change this shape as it is persisted in storage
export interface StoredSearchQuery {
pattern: string; pattern: string;
matchAll?: boolean; matchAll?: boolean;
matchCase?: boolean; matchCase?: boolean;
@ -37,34 +45,51 @@ export interface SearchPattern {
export interface GitSearch { export interface GitSearch {
repoPath: string; repoPath: string;
pattern: SearchPattern;
query: SearchQuery;
comparisonKey: string; comparisonKey: string;
results: Set<string>; results: Set<string>;
readonly paging?: { readonly paging?: {
readonly limit: number | undefined; readonly limit: number | undefined;
readonly startingCursor: string | undefined;
readonly more: boolean;
readonly hasMore: boolean;
}; };
more?(limit: number): Promise<GitSearch>; more?(limit: number): Promise<GitSearch>;
} }
export function getSearchPatternComparisonKey(search: SearchPattern) {
return `${search.pattern}|${search.matchAll ? 'A' : ''}${search.matchCase ? 'C' : ''}${
search.matchRegex ? 'R' : ''
}`;
export function getSearchQuery(search: StoredSearchQuery): SearchQuery {
return {
query: search.pattern,
matchAll: search.matchAll,
matchCase: search.matchCase,
matchRegex: search.matchRegex,
};
}
export function getStoredSearchQuery(search: SearchQuery): StoredSearchQuery {
return {
pattern: search.query,
matchAll: search.matchAll,
matchCase: search.matchCase,
matchRegex: search.matchRegex,
};
}
export function getSearchQueryComparisonKey(search: SearchQuery | StoredSearchQuery) {
return `${'query' in search ? search.query : search.pattern}|${search.matchAll ? 'A' : ''}${
search.matchCase ? 'C' : ''
}${search.matchRegex ? 'R' : ''}`;
} }
export function getSearchPatternFromCommit(ref: string): string;
export function getSearchPatternFromCommit(commit: GitRevisionReference): string;
export function getSearchPatternFromCommit(refOrCommit: string | GitRevisionReference) {
export function createSearchQueryForCommit(ref: string): string;
export function createSearchQueryForCommit(commit: GitRevisionReference): string;
export function createSearchQueryForCommit(refOrCommit: string | GitRevisionReference) {
return `#:${typeof refOrCommit === 'string' ? GitRevision.shorten(refOrCommit) : refOrCommit.name}`; return `#:${typeof refOrCommit === 'string' ? GitRevision.shorten(refOrCommit) : refOrCommit.name}`;
} }
export function getSearchPatternFromCommits(refs: string[]): string;
export function getSearchPatternFromCommits(commits: GitRevisionReference[]): string;
export function getSearchPatternFromCommits(refsOrCommits: (string | GitRevisionReference)[]) {
export function createSearchQueryForCommits(refs: string[]): string;
export function createSearchQueryForCommits(commits: GitRevisionReference[]): string;
export function createSearchQueryForCommits(refsOrCommits: (string | GitRevisionReference)[]) {
return refsOrCommits.map(r => `#:${typeof r === 'string' ? GitRevision.shorten(r) : r.name}`).join(' '); return refsOrCommits.map(r => `#:${typeof r === 'string' ? GitRevision.shorten(r) : r.name}`).join(' ');
} }
@ -85,7 +110,7 @@ const normalizeSearchOperatorsMap = new Map([
const searchOperationRegex = const searchOperationRegex =
/(?:(?<op>=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)\s?(?<value>".+?"|\S+\b}?))|(?<text>\S+)(?!(?:=|message|@|author|#|commit|\?|file|~|change):)/gi; /(?:(?<op>=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)\s?(?<value>".+?"|\S+\b}?))|(?<text>\S+)(?!(?:=|message|@|author|#|commit|\?|file|~|change):)/gi;
export function parseSearchOperations(search: string): Map<string, string[]> {
export function parseSearchQuery(query: string): Map<string, string[]> {
const operations = new Map<string, string[]>(); const operations = new Map<string, string[]>();
let op: SearchOperators | undefined; let op: SearchOperators | undefined;
@ -94,7 +119,7 @@ export function parseSearchOperations(search: string): Map {
let match; let match;
do { do {
match = searchOperationRegex.exec(search);
match = searchOperationRegex.exec(query);
if (match?.groups == null) break; if (match?.groups == null) break;
op = normalizeSearchOperatorsMap.get(match.groups.op as SearchOperators); op = normalizeSearchOperatorsMap.get(match.groups.op as SearchOperators);
@ -117,3 +142,78 @@ export function parseSearchOperations(search: string): Map {
return operations; return operations;
} }
const doubleQuoteRegex = /"/g;
export function getGitArgsFromSearchQuery(search: SearchQuery): {
args: string[];
files: string[];
shas?: Set<string> | undefined;
} {
const operations = parseSearchQuery(search.query);
const searchArgs = new Set<string>();
const files: string[] = [];
let shas;
let op;
let values = operations.get('commit:');
if (values != null) {
// searchArgs.add('-m');
for (const value of values) {
searchArgs.add(value.replace(doubleQuoteRegex, ''));
}
shas = searchArgs;
} else {
searchArgs.add('--all');
searchArgs.add('--full-history');
searchArgs.add(search.matchRegex ? '--extended-regexp' : '--fixed-strings');
if (search.matchRegex && !search.matchCase) {
searchArgs.add('--regexp-ignore-case');
}
for ([op, values] of operations.entries()) {
switch (op) {
case 'message:':
searchArgs.add('-m');
if (search.matchAll) {
searchArgs.add('--all-match');
}
for (const value of values) {
searchArgs.add(`--grep=${value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '')}`);
}
break;
case 'author:':
searchArgs.add('-m');
for (const value of values) {
searchArgs.add(`--author=${value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '')}`);
}
break;
case 'change:':
for (const value of values) {
searchArgs.add(
search.matchRegex
? `-G${value.replace(doubleQuoteRegex, '')}`
: `-S${value.replace(doubleQuoteRegex, '')}`,
);
}
break;
case 'file:':
for (const value of values) {
files.push(value.replace(doubleQuoteRegex, ''));
}
break;
}
}
}
return { args: [...searchArgs.values()], files: files, shas: shas };
}

+ 9
- 9
src/plus/github/githubGitProvider.ts View File

@ -73,8 +73,8 @@ import type { RemoteProvider } from '../../git/remotes/remoteProvider';
import type { RemoteProviders } from '../../git/remotes/remoteProviders'; import type { RemoteProviders } from '../../git/remotes/remoteProviders';
import { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/remoteProviders'; import { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/remoteProviders';
import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider'; import type { RichRemoteProvider } from '../../git/remotes/richRemoteProvider';
import type { GitSearch, SearchPattern } from '../../git/search';
import { getSearchPatternComparisonKey, parseSearchOperations } from '../../git/search';
import type { GitSearch, SearchQuery } from '../../git/search';
import { getSearchQueryComparisonKey, parseSearchQuery } from '../../git/search';
import type { LogScope } from '../../logger'; import type { LogScope } from '../../logger';
import { Logger } from '../../logger'; import { Logger } from '../../logger';
import { gate } from '../../system/decorators/gate'; import { gate } from '../../system/decorators/gate';
@ -1215,7 +1215,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
limit: log.limit, limit: log.limit,
// endingCursor: log.endingCursor, // endingCursor: log.endingCursor,
startingCursor: log.startingCursor, startingCursor: log.startingCursor,
more: log.hasMore,
hasMore: log.hasMore,
}, },
more: async (limit: number | { until: string } | undefined): Promise<GitGraph | undefined> => { more: async (limit: number | { until: string } | undefined): Promise<GitGraph | undefined> => {
const moreLog = await log.more?.(limit); const moreLog = await log.more?.(limit);
@ -1584,15 +1584,15 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@log() @log()
async searchForCommitsSimple( async searchForCommitsSimple(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
_options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, _options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' },
): Promise<GitSearch> { ): Promise<GitSearch> {
search = { matchAll: false, matchCase: false, matchRegex: true, ...search }; search = { matchAll: false, matchCase: false, matchRegex: true, ...search };
const comparisonKey = getSearchPatternComparisonKey(search);
const comparisonKey = getSearchQueryComparisonKey(search);
return { return {
repoPath: repoPath, repoPath: repoPath,
pattern: search,
query: search,
comparisonKey: comparisonKey, comparisonKey: comparisonKey,
results: new Set<string>(), results: new Set<string>(),
}; };
@ -1685,14 +1685,14 @@ export class GitHubGitProvider implements GitProvider, Disposable {
@log() @log()
async getLogForSearch( async getLogForSearch(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
options?: { cursor?: string; limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, options?: { cursor?: string; limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
): Promise<GitLog | undefined> { ): Promise<GitLog | undefined> {
if (repoPath == null) return undefined; if (repoPath == null) return undefined;
const scope = getLogScope(); const scope = getLogScope();
const operations = parseSearchOperations(search.pattern);
const operations = parseSearchQuery(search.query);
let op; let op;
let values = operations.get('commit:'); let values = operations.get('commit:');
@ -1838,7 +1838,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
private getLogForSearchMoreFn( private getLogForSearchMoreFn(
log: GitLog, log: GitLog,
search: SearchPattern,
search: SearchQuery,
options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number },
): (limit: number | undefined) => Promise<GitLog> { ): (limit: number | undefined) => Promise<GitLog> {
return async (limit: number | undefined) => { return async (limit: number | undefined) => {

+ 8
- 15
src/plus/webviews/graph/graphWebview.ts View File

@ -14,7 +14,7 @@ import type { GitGraph } from '../../../git/models/graph';
import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository'; import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository';
import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository'; import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository';
import type { GitSearch } from '../../../git/search'; import type { GitSearch } from '../../../git/search';
import { getSearchPatternComparisonKey } from '../../../git/search';
import { getSearchQueryComparisonKey } from '../../../git/search';
import { registerCommand } from '../../../system/command'; import { registerCommand } from '../../../system/command';
import { gate } from '../../../system/decorators/gate'; import { gate } from '../../../system/decorators/gate';
import { debug } from '../../../system/decorators/log'; import { debug } from '../../../system/decorators/log';
@ -432,9 +432,8 @@ export class GraphWebview extends WebviewBase {
private async onSearchCommits(e: SearchCommitsParams, completionId?: string) { private async onSearchCommits(e: SearchCommitsParams, completionId?: string) {
let search: GitSearch | undefined = this._search; let search: GitSearch | undefined = this._search;
if (search?.more != null && e.more && search.comparisonKey === getSearchPatternComparisonKey(e.search)) {
const limit = typeof e.more !== 'boolean' ? e.more.limit : undefined;
search = await search.more(limit ?? configuration.get('graph.searchItemLimit') ?? 100);
if (e.more && search?.more != null && search.comparisonKey === getSearchQueryComparisonKey(e.search)) {
search = await search.more(e.limit ?? configuration.get('graph.searchItemLimit') ?? 100);
if (search != null) { if (search != null) {
this._search = search; this._search = search;
@ -443,10 +442,7 @@ export class GraphWebview extends WebviewBase {
{ {
results: { results: {
ids: [...search.results.values()], ids: [...search.results.values()],
paging: {
startingCursor: search.paging?.startingCursor,
more: search.paging?.more ?? false,
},
paging: { hasMore: search.paging?.hasMore ?? false },
}, },
selectedRows: this._selectedRows, selectedRows: this._selectedRows,
}, },
@ -457,7 +453,7 @@ export class GraphWebview extends WebviewBase {
return; return;
} }
if (search == null || search.comparisonKey !== getSearchPatternComparisonKey(e.search)) {
if (search == null || search.comparisonKey !== getSearchQueryComparisonKey(e.search)) {
if (this._repository == null) return; if (this._repository == null) return;
if (this._repository.etag !== this._etagRepository) { if (this._repository.etag !== this._etagRepository) {
@ -499,10 +495,7 @@ export class GraphWebview extends WebviewBase {
{ {
results: { results: {
ids: [...search.results.values()], ids: [...search.results.values()],
paging: {
startingCursor: search.paging?.startingCursor,
more: search.paging?.more ?? false,
},
paging: { hasMore: search.paging?.hasMore ?? false },
}, },
selectedRows: this._selectedRows, selectedRows: this._selectedRows,
}, },
@ -636,7 +629,7 @@ export class GraphWebview extends WebviewBase {
selectedRows: this._selectedRows, selectedRows: this._selectedRows,
paging: { paging: {
startingCursor: data.paging?.startingCursor, startingCursor: data.paging?.startingCursor,
more: data.paging?.more ?? false,
hasMore: data.paging?.hasMore ?? false,
}, },
}, },
completionId, completionId,
@ -729,7 +722,7 @@ export class GraphWebview extends WebviewBase {
data != null data != null
? { ? {
startingCursor: data.paging?.startingCursor, startingCursor: data.paging?.startingCursor,
more: data.paging?.more ?? false,
hasMore: data.paging?.hasMore ?? false,
} }
: undefined, : undefined,
config: this.getComponentConfig(), config: this.getComponentConfig(),

+ 6
- 11
src/plus/webviews/graph/protocol.ts View File

@ -2,7 +2,7 @@ import type { GraphRow, Remote } from '@gitkraken/gitkraken-components';
import type { DateStyle, GraphColumnConfig } from '../../../config'; import type { DateStyle, GraphColumnConfig } from '../../../config';
import type { RepositoryVisibility } from '../../../git/gitProvider'; import type { RepositoryVisibility } from '../../../git/gitProvider';
import type { GitGraphRowType } from '../../../git/models/graph'; import type { GitGraphRowType } from '../../../git/models/graph';
import type { SearchPattern } from '../../../git/search';
import type { SearchQuery } from '../../../git/search';
import type { Subscription } from '../../../subscription'; import type { Subscription } from '../../../subscription';
import type { DateTimeFormat } from '../../../system/date'; import type { DateTimeFormat } from '../../../system/date';
import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol'; import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol';
@ -30,7 +30,7 @@ export interface State {
export interface GraphPaging { export interface GraphPaging {
startingCursor?: string; startingCursor?: string;
more: boolean;
hasMore: boolean;
} }
export interface GraphRepository { export interface GraphRepository {
@ -96,9 +96,9 @@ export interface GetMoreCommitsParams {
export const GetMoreCommitsCommandType = new IpcCommandType<GetMoreCommitsParams>('graph/getMoreCommits'); export const GetMoreCommitsCommandType = new IpcCommandType<GetMoreCommitsParams>('graph/getMoreCommits');
export interface SearchCommitsParams { export interface SearchCommitsParams {
search: SearchPattern;
more?: boolean | { limit?: number };
search: SearchQuery;
limit?: number;
more?: boolean;
} }
export const SearchCommitsCommandType = new IpcCommandType<SearchCommitsParams>('graph/searchCommits'); export const SearchCommitsCommandType = new IpcCommandType<SearchCommitsParams>('graph/searchCommits');
@ -174,12 +174,7 @@ export const DidEnsureCommitNotificationType = new IpcNotificationType
); );
export interface DidSearchCommitsParams { export interface DidSearchCommitsParams {
results:
| {
ids: string[];
paging?: GraphPaging;
}
| undefined;
results: { ids: string[]; paging?: { hasMore: boolean } } | undefined;
selectedRows?: { [id: string]: true }; selectedRows?: { [id: string]: true };
} }
export const DidSearchCommitsNotificationType = new IpcNotificationType<DidSearchCommitsParams>( export const DidSearchCommitsNotificationType = new IpcNotificationType<DidSearchCommitsParams>(

+ 2
- 2
src/storage.ts View File

@ -1,7 +1,7 @@
import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode'; import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode';
import { EventEmitter } from 'vscode'; import { EventEmitter } from 'vscode';
import type { GraphColumnConfig, ViewShowBranchComparison } from './config'; import type { GraphColumnConfig, ViewShowBranchComparison } from './config';
import type { SearchPattern } from './git/search';
import type { StoredSearchQuery } from './git/search';
import type { Subscription } from './subscription'; import type { Subscription } from './subscription';
import type { TrackedUsage, TrackedUsageKeys } from './usageTracker'; import type { TrackedUsage, TrackedUsageKeys } from './usageTracker';
import type { CompletedActions } from './webviews/home/protocol'; import type { CompletedActions } from './webviews/home/protocol';
@ -239,7 +239,7 @@ export interface StoredPinnedSearch {
resultsType?: { singular: string; plural: string }; resultsType?: { singular: string; plural: string };
}; };
}; };
search: SearchPattern;
search: StoredSearchQuery;
} }
export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch; export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch;

+ 11
- 11
src/views/nodes/searchResultsNode.ts View File

@ -3,8 +3,8 @@ import { ThemeIcon } from 'vscode';
import { executeGitCommand } from '../../commands/gitCommands.actions'; import { executeGitCommand } from '../../commands/gitCommands.actions';
import { GitUri } from '../../git/gitUri'; import { GitUri } from '../../git/gitUri';
import type { GitLog } from '../../git/models/log'; import type { GitLog } from '../../git/models/log';
import type { SearchPattern } from '../../git/search';
import { getSearchPatternComparisonKey } from '../../git/search';
import type { SearchQuery, StoredSearchQuery } from '../../git/search';
import { getSearchQueryComparisonKey, getStoredSearchQuery } from '../../git/search';
import { gate } from '../../system/decorators/gate'; import { gate } from '../../system/decorators/gate';
import { debug, log } from '../../system/decorators/log'; import { debug, log } from '../../system/decorators/log';
import { md5, pluralize } from '../../system/string'; import { md5, pluralize } from '../../system/string';
@ -26,14 +26,14 @@ interface SearchQueryResults {
export class SearchResultsNode extends ViewNode<SearchAndCompareView> implements PageableViewNode { export class SearchResultsNode extends ViewNode<SearchAndCompareView> implements PageableViewNode {
static key = ':search-results'; static key = ':search-results';
static getId(repoPath: string, search: SearchPattern | undefined, instanceId: number): string {
static getId(repoPath: string, search: SearchQuery | undefined, instanceId: number): string {
return `${RepositoryNode.getId(repoPath)}${this.key}(${ return `${RepositoryNode.getId(repoPath)}${this.key}(${
search == null ? '?' : getSearchPatternComparisonKey(search)
search == null ? '?' : getSearchQueryComparisonKey(search)
}):${instanceId}`; }):${instanceId}`;
} }
static getPinnableId(repoPath: string, search: SearchPattern) {
return md5(`${repoPath}|${getSearchPatternComparisonKey(search)}`);
static getPinnableId(repoPath: string, search: SearchQuery | StoredSearchQuery) {
return md5(`${repoPath}|${getSearchQueryComparisonKey(search)}`);
} }
private _instanceId: number; private _instanceId: number;
@ -41,7 +41,7 @@ export class SearchResultsNode extends ViewNode implements
view: SearchAndCompareView, view: SearchAndCompareView,
parent: ViewNode, parent: ViewNode,
public readonly repoPath: string, public readonly repoPath: string,
search: SearchPattern,
search: SearchQuery,
private _labels: { private _labels: {
label: string; label: string;
queryLabel: queryLabel:
@ -83,8 +83,8 @@ export class SearchResultsNode extends ViewNode implements
return this._pinned !== 0; return this._pinned !== 0;
} }
private _search: SearchPattern;
get search(): SearchPattern {
private _search: SearchQuery;
get search(): SearchQuery {
return this._search; return this._search;
} }
@ -153,7 +153,7 @@ export class SearchResultsNode extends ViewNode implements
} }
async edit(search?: { async edit(search?: {
pattern: SearchPattern;
pattern: SearchQuery;
labels: { labels: {
label: string; label: string;
queryLabel: queryLabel:
@ -296,7 +296,7 @@ export class SearchResultsNode extends ViewNode implements
timestamp: this._pinned, timestamp: this._pinned,
path: this.repoPath, path: this.repoPath,
labels: this._labels, labels: this._labels,
search: this.search,
search: getStoredSearchQuery(this.search),
}); });
} }
} }

+ 12
- 3
src/views/searchAndCompareView.ts View File

@ -8,7 +8,8 @@ import { setContext } from '../context';
import { unknownGitUri } from '../git/gitUri'; import { unknownGitUri } from '../git/gitUri';
import type { GitLog } from '../git/models/log'; import type { GitLog } from '../git/models/log';
import { GitRevision } from '../git/models/reference'; import { GitRevision } from '../git/models/reference';
import type { SearchPattern } from '../git/search';
import type { SearchQuery } from '../git/search';
import { getSearchQuery } from '../git/search';
import { ReferencePicker, ReferencesQuickPickIncludes } from '../quickpicks/referencePicker'; import { ReferencePicker, ReferencesQuickPickIncludes } from '../quickpicks/referencePicker';
import { RepositoryPicker } from '../quickpicks/repositoryPicker'; import { RepositoryPicker } from '../quickpicks/repositoryPicker';
import type { StoredNamedRef, StoredPinnedItem, StoredPinnedItems } from '../storage'; import type { StoredNamedRef, StoredPinnedItem, StoredPinnedItems } from '../storage';
@ -377,7 +378,7 @@ export class SearchAndCompareView extends ViewBase
async search( async search(
repoPath: string, repoPath: string,
search: SearchPattern,
search: SearchQuery,
{ {
label, label,
reveal, reveal,
@ -469,7 +470,15 @@ export class SearchAndCompareView extends ViewBase
migratedPins[k] = p; migratedPins[k] = p;
} }
return new SearchResultsNode(this, root, p.path, p.search, p.labels, undefined, p.timestamp);
return new SearchResultsNode(
this,
root,
p.path,
getSearchQuery(p.search),
p.labels,
undefined,
p.timestamp,
);
}); });
if (migrated) { if (migrated) {

+ 18
- 18
src/webviews/apps/plus/graph/GraphWrapper.tsx View File

@ -14,7 +14,7 @@ import { DateStyle } from '../../../../config';
import type { GraphColumnConfig } from '../../../../config'; import type { GraphColumnConfig } from '../../../../config';
import { RepositoryVisibility } from '../../../../git/gitProvider'; import { RepositoryVisibility } from '../../../../git/gitProvider';
import type { GitGraphRowType } from '../../../../git/models/graph'; import type { GitGraphRowType } from '../../../../git/models/graph';
import type { SearchPattern } from '../../../../git/search';
import type { SearchQuery } from '../../../../git/search';
import type { import type {
DidEnsureCommitParams, DidEnsureCommitParams,
DidSearchCommitsParams, DidSearchCommitsParams,
@ -38,10 +38,10 @@ export interface GraphWrapperProps extends State {
onColumnChange?: (name: string, settings: GraphColumnConfig) => void; onColumnChange?: (name: string, settings: GraphColumnConfig) => void;
onMissingAvatars?: (emails: { [email: string]: string }) => void; onMissingAvatars?: (emails: { [email: string]: string }) => void;
onMoreCommits?: (id?: string) => void; onMoreCommits?: (id?: string) => void;
onSearchCommits?: (search: SearchPattern | undefined) => void;
onSearchCommits?: (search: SearchQuery | undefined, options?: { limit?: number }) => void;
onSearchCommitsPromise?: ( onSearchCommitsPromise?: (
search: SearchPattern,
options?: { more?: boolean | { limit?: number } },
search: SearchQuery,
options?: { limit?: number; more?: boolean },
) => Promise<DidSearchCommitsParams>; ) => Promise<DidSearchCommitsParams>;
onDismissBanner?: (key: DismissBannerParams['key']) => void; onDismissBanner?: (key: DismissBannerParams['key']) => void;
onSelectionChange?: (selection: { id: string; type: GitGraphRowType }[]) => void; onSelectionChange?: (selection: { id: string; type: GitGraphRowType }[]) => void;
@ -248,10 +248,10 @@ export function GraphWrapper({
// column setting UI // column setting UI
const [columnSettingsExpanded, setColumnSettingsExpanded] = useState(false); const [columnSettingsExpanded, setColumnSettingsExpanded] = useState(false);
// search state // search state
const [search, setSearch] = useState<SearchPattern | undefined>(undefined);
const [searchQuery, setSearchQuery] = useState<SearchQuery | undefined>(undefined);
const [searchResultKey, setSearchResultKey] = useState<string | undefined>(undefined); const [searchResultKey, setSearchResultKey] = useState<string | undefined>(undefined);
const [searchResultIds, setSearchResultIds] = useState(searchResults?.ids); const [searchResultIds, setSearchResultIds] = useState(searchResults?.ids);
const [hasMoreSearchResults, setHasMoreSearchResults] = useState(searchResults?.paging?.more ?? false);
const [hasMoreSearchResults, setHasMoreSearchResults] = useState(searchResults?.paging?.hasMore ?? false);
useEffect(() => { useEffect(() => {
if (graphRows.length === 0) { if (graphRows.length === 0) {
@ -289,8 +289,8 @@ export function GraphWrapper({
if (next) { if (next) {
if (rowIndex < resultIds.length - 1) { if (rowIndex < resultIds.length - 1) {
rowIndex++; rowIndex++;
} else if (search != null && hasMoreSearchResults) {
const results = await onSearchCommitsPromise?.(search, { more: true });
} else if (searchQuery != null && hasMoreSearchResults) {
const results = await onSearchCommitsPromise?.(searchQuery, { more: true });
if (results?.results != null) { if (results?.results != null) {
if (resultIds.length < results.results.ids.length) { if (resultIds.length < results.results.ids.length) {
resultIds = results.results.ids; resultIds = results.results.ids;
@ -307,8 +307,8 @@ export function GraphWrapper({
} else if (rowIndex > 0) { } else if (rowIndex > 0) {
rowIndex--; rowIndex--;
} else { } else {
if (search != null && hasMoreSearchResults) {
const results = await onSearchCommitsPromise?.(search, { more: { limit: 0 } });
if (searchQuery != null && hasMoreSearchResults) {
const results = await onSearchCommitsPromise?.(searchQuery, { limit: 0, more: true });
if (results?.results != null) { if (results?.results != null) {
if (resultIds.length < results.results.ids.length) { if (resultIds.length < results.results.ids.length) {
resultIds = results.results.ids; resultIds = results.results.ids;
@ -347,11 +347,11 @@ export function GraphWrapper({
} }
}; };
const handleSearchInput = (e: CustomEvent<SearchPattern>) => {
const handleSearchInput = (e: CustomEvent<SearchQuery>) => {
const detail = e.detail; const detail = e.detail;
setSearch(detail);
setSearchQuery(detail);
const isValid = detail.pattern.length >= 3;
const isValid = detail.query.length >= 3;
if (!isValid) { if (!isValid) {
setSearchResultKey(undefined); setSearchResultKey(undefined);
setSearchResultIds(undefined); setSearchResultIds(undefined);
@ -394,7 +394,7 @@ export function GraphWrapper({
setIsPrivateRepo(state.selectedRepositoryVisibility === RepositoryVisibility.Private); setIsPrivateRepo(state.selectedRepositoryVisibility === RepositoryVisibility.Private);
setIsLoading(state.loading); setIsLoading(state.loading);
setSearchResultIds(state.searchResults?.ids); setSearchResultIds(state.searchResults?.ids);
setHasMoreSearchResults(state.searchResults?.paging?.more ?? false);
setHasMoreSearchResults(state.searchResults?.paging?.hasMore ?? false);
} }
useEffect(() => subscriber?.(transformData), []); useEffect(() => subscriber?.(transformData), []);
@ -621,8 +621,8 @@ export function GraphWrapper({
<header className="titlebar graph-app__header"> <header className="titlebar graph-app__header">
<div className="titlebar__group"> <div className="titlebar__group">
<SearchField <SearchField
value={search?.pattern}
onChange={e => handleSearchInput(e as CustomEvent<SearchPattern>)}
value={searchQuery?.query}
onChange={e => handleSearchInput(e as CustomEvent<SearchQuery>)}
onPrevious={() => handleSearchNavigation(false)} onPrevious={() => handleSearchNavigation(false)}
onNext={() => handleSearchNavigation(true)} onNext={() => handleSearchNavigation(true)}
/> />
@ -630,7 +630,7 @@ export function GraphWrapper({
aria-label="Graph search navigation" aria-label="Graph search navigation"
step={searchPosition} step={searchPosition}
total={searchResultIds?.length ?? 0} total={searchResultIds?.length ?? 0}
valid={Boolean(search?.pattern && search.pattern.length > 2)}
valid={Boolean(searchQuery?.query && searchQuery.query.length > 2)}
more={hasMoreSearchResults} more={hasMoreSearchResults}
onPrevious={() => handleSearchNavigation(false)} onPrevious={() => handleSearchNavigation(false)}
onNext={() => handleSearchNavigation(true)} onNext={() => handleSearchNavigation(true)}
@ -656,7 +656,7 @@ export function GraphWrapper({
formatCommitDateTime={getGraphDateFormatter(graphConfig)} formatCommitDateTime={getGraphDateFormatter(graphConfig)}
getExternalIcon={getIconElementLibrary} getExternalIcon={getIconElementLibrary}
graphRows={graphRows} graphRows={graphRows}
hasMoreCommits={pagingState?.more}
hasMoreCommits={pagingState?.hasMore}
height={mainHeight} height={mainHeight}
highlightedShas={searchHighlights} highlightedShas={searchHighlights}
// highlightRowssOnRefHover={graphConfig?.highlightRowsOnRefHover} // highlightRowssOnRefHover={graphConfig?.highlightRowsOnRefHover}

+ 5
- 5
src/webviews/apps/plus/graph/graph.tsx View File

@ -4,7 +4,7 @@ import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom'; import { render, unmountComponentAtNode } from 'react-dom';
import type { GraphColumnConfig } from '../../../../config'; import type { GraphColumnConfig } from '../../../../config';
import type { GitGraphRowType } from '../../../../git/models/graph'; import type { GitGraphRowType } from '../../../../git/models/graph';
import type { SearchPattern } from '../../../../git/search';
import type { SearchQuery } from '../../../../git/search';
import type { import type {
DismissBannerParams, DismissBannerParams,
GraphRepository, GraphRepository,
@ -299,18 +299,18 @@ export class GraphApp extends App {
return this.sendCommand(GetMoreCommitsCommandType, { sha: sha }); return this.sendCommand(GetMoreCommitsCommandType, { sha: sha });
} }
private onSearchCommits(search: SearchPattern | undefined) {
private onSearchCommits(search: SearchQuery | undefined, options?: { limit?: number }) {
if (search == null) { if (search == null) {
this.state.searchResults = undefined; this.state.searchResults = undefined;
return; return;
} }
return this.sendCommand(SearchCommitsCommandType, { search: search });
return this.sendCommand(SearchCommitsCommandType, { search: search, limit: options?.limit });
} }
private onSearchCommitsPromise(search: SearchPattern, options?: { more?: boolean | { limit?: number } }) {
private onSearchCommitsPromise(search: SearchQuery, options?: { limit?: number; more?: boolean }) {
return this.sendCommandWithCompletion( return this.sendCommandWithCompletion(
SearchCommitsCommandType, SearchCommitsCommandType,
{ search: search, more: options?.more },
{ search: search, limit: options?.limit, more: options?.more },
DidSearchCommitsNotificationType, DidSearchCommitsNotificationType,
); );
} }

+ 5
- 3
src/webviews/apps/shared/components/search/search-field.ts View File

@ -1,4 +1,5 @@
import { attr, css, customElement, FASTElement, html } from '@microsoft/fast-element'; import { attr, css, customElement, FASTElement, html } from '@microsoft/fast-element';
import type { SearchQuery } from '../../../../../git/search';
import '../codicon'; import '../codicon';
// match case is disabled unless regex is true // match case is disabled unless regex is true
@ -191,11 +192,12 @@ export class SearchField extends FASTElement {
} }
emitSearch() { emitSearch() {
this.$emit('change', {
pattern: this.value,
const search: SearchQuery = {
query: this.value,
matchAll: this.all, matchAll: this.all,
matchCase: this.case, matchCase: this.case,
matchRegex: this.regex, matchRegex: this.regex,
});
};
this.$emit('change', search);
} }
} }

+ 2
- 2
src/webviews/rebase/rebaseEditor.ts View File

@ -495,7 +495,7 @@ export class RebaseEditorProvider implements CustomTextEditorProvider, Disposabl
const html = content.replace( const html = content.replace(
/#{(head|body|endOfBody|placement|cspSource|cspNonce|root|webroot)}/g, /#{(head|body|endOfBody|placement|cspSource|cspNonce|root|webroot)}/g,
(_substring, token) => {
(_substring: string, token: string) => {
switch (token) { switch (token) {
case 'endOfBody': case 'endOfBody':
return `<script type="text/javascript" nonce="${cspNonce}">window.bootstrap=${JSON.stringify( return `<script type="text/javascript" nonce="${cspNonce}">window.bootstrap=${JSON.stringify(
@ -541,7 +541,7 @@ async function parseRebaseTodo(
const commits: Commit[] = []; const commits: Commit[] = [];
const log = await container.git.getLogForSearch(repoPath, { const log = await container.git.getLogForSearch(repoPath, {
pattern: `${onto ? `#:${onto} ` : ''}${join(
query: `${onto ? `#:${onto} ` : ''}${join(
map(entries, e => `#:${e.ref}`), map(entries, e => `#:${e.ref}`),
' ', ' ',
)}`, )}`,

Loading…
Cancel
Save