diff --git a/src/commands/git/search.ts b/src/commands/git/search.ts index bf0d372..cec4170 100644 --- a/src/commands/git/search.ts +++ b/src/commands/git/search.ts @@ -5,8 +5,8 @@ import { getContext } from '../../context'; import type { GitCommit } from '../../git/models/commit'; import type { GitLog } from '../../git/models/log'; 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 { ActionQuickPickItem } from '../../quickpicks/items/common'; import { pluralize } from '../../system/string'; @@ -34,7 +34,7 @@ interface Context { title: string; } -interface State extends Required { +interface State extends Required { repo: string | Repository; openPickInView?: boolean; showResultsInSideBar: boolean | SearchResultsNode; @@ -73,7 +73,7 @@ export class SearchGitCommand extends QuickCommand { counter++; } - if (args?.state?.pattern != null && !args.prefillOnly) { + if (args?.state?.query != null && !args.prefillOnly) { 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); if (result === StepResult.Break) { // 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.pattern = undefined; + state.query = undefined; continue; } - state.pattern = result; + state.query = result; } - const search: SearchPattern = { - pattern: state.pattern, + const search: SearchQuery = { + query: state.query, matchAll: state.matchAll, matchCase: state.matchCase, matchRegex: state.matchRegex, }; - const searchKey = getSearchPatternComparisonKey(search); + const searchKey = getSearchQueryComparisonKey(search); if (context.resultsPromise == null || context.resultsKey !== searchKey) { context.resultsPromise = state.repo.searchForCommits(search); @@ -178,7 +178,7 @@ export class SearchGitCommand extends QuickCommand { state.repo.path, search, { - label: { label: `for ${state.pattern}` }, + label: { label: `for ${state.query}` }, }, context.resultsPromise, state.showResultsInSideBar instanceof SearchResultsNode ? state.showResultsInSideBar : undefined, @@ -195,10 +195,10 @@ export class SearchGitCommand extends QuickCommand { onDidLoadMore: log => (context.resultsPromise = Promise.resolve(log)), placeholder: (context, log) => log == null - ? `No results for ${state.pattern}` + ? `No results for ${state.query}` : `${pluralize('result', log.count, { format: c => (log.hasMore ? `${c}+` : undefined), - })} for ${state.pattern}`, + })} for ${state.query}`, picked: context.commit?.ref, showInSideBarCommand: new ActionQuickPickItem( '$(link-external) Show Results in Side Bar', @@ -207,7 +207,7 @@ export class SearchGitCommand extends QuickCommand { repoPath, search, { - label: { label: `for ${state.pattern}` }, + label: { label: `for ${state.query}` }, reveal: { select: true, focus: false, @@ -224,7 +224,7 @@ export class SearchGitCommand extends QuickCommand { repoPath, search, { - label: { label: `for ${state.pattern}` }, + label: { label: `for ${state.query}` }, reveal: { select: true, focus: false, @@ -317,7 +317,7 @@ export class SearchGitCommand extends QuickCommand { matchOnDetail: true, additionalButtons: [matchCaseButton, matchAllButton, matchRegexButton], items: items, - value: state.pattern, + value: state.query, selectValueWhenShown: false, onDidAccept: (quickpick): boolean => { const pick = quickpick.selectedItems[0]; @@ -351,7 +351,7 @@ export class SearchGitCommand extends QuickCommand { // Simulate an extra step if we have a value state.counter = value ? 3 : 2; - const operations = parseSearchOperations(value); + const operations = parseSearchQuery(value); quickpick.title = appendReposToTitle( operations.size === 0 || operations.size > 1 diff --git a/src/commands/searchCommits.ts b/src/commands/searchCommits.ts index ec13688..c1aee38 100644 --- a/src/commands/searchCommits.ts +++ b/src/commands/searchCommits.ts @@ -2,14 +2,14 @@ import { executeGitCommand } from '../commands/gitCommands.actions'; import { configuration } from '../configuration'; import { Commands } from '../constants'; import type { Container } from '../container'; -import type { SearchPattern } from '../git/search'; +import type { SearchQuery } from '../git/search'; import { command } from '../system/command'; import { SearchResultsNode } from '../views/nodes/searchResultsNode'; import type { CommandContext } from './base'; import { Command, isCommandContextViewNodeHasRepository } from './base'; export interface SearchCommitsCommandArgs { - search?: Partial; + search?: Partial; repoPath?: string; prefillOnly?: boolean; diff --git a/src/commands/showCommitsInView.ts b/src/commands/showCommitsInView.ts index 3c2df1f..940c402 100644 --- a/src/commands/showCommitsInView.ts +++ b/src/commands/showCommitsInView.ts @@ -3,7 +3,7 @@ import { executeGitCommand } from '../commands/gitCommands.actions'; import { Commands } from '../constants'; import type { Container } from '../container'; import { GitUri } from '../git/gitUri'; -import { getSearchPatternFromCommits } from '../git/search'; +import { createSearchQueryForCommits } from '../git/search'; import { Logger } from '../logger'; import { showFileNotUnderSourceControlWarningMessage, showGenericErrorMessage } from '../messages'; import { command } from '../system/command'; @@ -88,7 +88,7 @@ export class ShowCommitsInViewCommand extends ActiveEditorCommand { command: 'search', state: { repo: args?.repoPath, - pattern: getSearchPatternFromCommits(args.refs), + query: createSearchQueryForCommits(args.refs), showResultsInSideBar: true, }, }); diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 5d1f92e..0de0939 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -106,8 +106,8 @@ import type { RemoteProvider } from '../../../git/remotes/remoteProvider'; import type { RemoteProviders } from '../../../git/remotes/remoteProviders'; import { getRemoteProviderMatcher, loadRemoteProviders } from '../../../git/remotes/remoteProviders'; 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 type { LogScope } from '../../../logger'; import { @@ -155,7 +155,6 @@ const RepoSearchWarnings = { doesNotExist: /no such file or directory/i, }; -const doubleQuoteRegex = /"/g; const driveLetterRegex = /(?<=^\/?)([a-zA-Z])(?=:\/)/; const userConfigRegex = /^user\.(name|email) (.*)$/gm; const mappedAuthorRegex = /(.+)\s<(.+)>/; @@ -1859,7 +1858,7 @@ export class LocalGitProvider implements GitProvider, Disposable { paging: { limit: limit === 0 ? count : limit, startingCursor: startingCursor, - more: count > limit, + hasMore: count > limit, }, more: async (limit: number, sha?: string): Promise => getCommitsForGraphCore.call(this, limit, sha, cursor), @@ -2640,20 +2639,20 @@ export class LocalGitProvider implements GitProvider, Disposable { @log() async searchForCommitsSimple( repoPath: string, - search: SearchPattern, + search: SearchQuery, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, ): Promise { search = { matchAll: false, matchCase: false, matchRegex: true, ...search }; - const comparisonKey = getSearchPatternComparisonKey(search); + const comparisonKey = getSearchQueryComparisonKey(search); try { - const { args: searchArgs, files, commits } = this.getArgsFromSearchPattern(search); - if (commits?.size) { + const { args: searchArgs, files, shas } = getGitArgsFromSearchQuery(search); + if (shas?.size) { return { repoPath: repoPath, - pattern: search, + query: search, comparisonKey: comparisonKey, - results: commits, + results: shas, }; } @@ -2683,7 +2682,7 @@ export class LocalGitProvider implements GitProvider, Disposable { if (options?.cancellation?.isCancellationRequested) { // 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( @@ -2699,7 +2698,7 @@ export class LocalGitProvider implements GitProvider, Disposable { if (options?.cancellation?.isCancellationRequested) { // 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; @@ -2722,15 +2721,14 @@ export class LocalGitProvider implements GitProvider, Disposable { return { repoPath: repoPath, - pattern: search, + query: search, comparisonKey: comparisonKey, results: results, paging: limit !== 0 && count > limit ? { limit: limit, - startingCursor: cursor?.sha, - more: true, + hasMore: true, } : undefined, more: async (limit: number): Promise => searchForCommitsCore.call(this, limit, cursor), @@ -2740,10 +2738,10 @@ export class LocalGitProvider implements GitProvider, Disposable { return searchForCommitsCore.call(this, limit); } catch (ex) { // 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 { repoPath: repoPath, - pattern: search, + query: search, comparisonKey: comparisonKey, results: new Set(), }; @@ -2753,7 +2751,7 @@ export class LocalGitProvider implements GitProvider, Disposable { @log() async getLogForSearch( repoPath: string, - search: SearchPattern, + search: SearchQuery, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, ): Promise { 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 similarityThreshold = configuration.get('advanced.similarityThreshold'); - const { args, files, commits } = this.getArgsFromSearchPattern(search); + const { args, files, shas } = getGitArgsFromSearchQuery(search); args.push(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`, '--'); if (files.length !== 0) { @@ -2773,7 +2771,7 @@ export class LocalGitProvider implements GitProvider, Disposable { ordering: configuration.get('advanced.commitOrdering'), ...options, limit: limit, - useShow: Boolean(commits?.size), + useShow: Boolean(shas?.size), }); const log = GitLogParser.parse( this.container, @@ -2802,84 +2800,9 @@ export class LocalGitProvider implements GitProvider, Disposable { } } - private getArgsFromSearchPattern(search: SearchPattern): { - args: string[]; - files: string[]; - commits?: Set | undefined; - } { - const operations = parseSearchOperations(search.pattern); - - const searchArgs = new Set(); - 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( log: GitLog, - search: SearchPattern, + search: SearchQuery, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null }, ): (limit: number | undefined) => Promise { return async (limit: number | undefined) => { diff --git a/src/git/gitProvider.ts b/src/git/gitProvider.ts index bd3578b..b980ecf 100644 --- a/src/git/gitProvider.ts +++ b/src/git/gitProvider.ts @@ -26,7 +26,7 @@ import type { GitWorktree } from './models/worktree'; import type { RemoteProvider } from './remotes/remoteProvider'; import type { RemoteProviders } from './remotes/remoteProviders'; import type { RichRemoteProvider } from './remotes/richRemoteProvider'; -import type { GitSearch, SearchPattern } from './search'; +import type { GitSearch, SearchQuery } from './search'; export const enum GitProviderId { Git = 'git', @@ -298,12 +298,12 @@ export interface GitProvider extends Disposable { ): Promise | undefined>; searchForCommitsSimple( repoPath: string | Uri, - search: SearchPattern, + search: SearchQuery, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, ): Promise; getLogForSearch( repoPath: string, - search: SearchPattern, + search: SearchQuery, options?: { limit?: number | undefined; ordering?: 'date' | 'author-date' | 'topo' | null | undefined; diff --git a/src/git/gitProviderService.ts b/src/git/gitProviderService.ts index c9645fb..791384c 100644 --- a/src/git/gitProviderService.ts +++ b/src/git/gitProviderService.ts @@ -70,7 +70,7 @@ import type { GitWorktree } from './models/worktree'; import { RichRemoteProviders } from './remotes/remoteProviderConnections'; import type { RemoteProviders } from './remotes/remoteProviders'; import type { RichRemoteProvider } from './remotes/richRemoteProvider'; -import type { GitSearch, SearchPattern } from './search'; +import type { GitSearch, SearchQuery } from './search'; const maxDefaultBranchWeight = 100; const weightedDefaultBranches = new Map([ @@ -1468,7 +1468,7 @@ export class GitProviderService implements Disposable { @log() searchForCommitsSimple( repoPath: string | Uri, - search: SearchPattern, + search: SearchQuery, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, ): Promise { const { provider, path } = this.getProvider(repoPath); @@ -1478,7 +1478,7 @@ export class GitProviderService implements Disposable { @log() async getLogForSearch( repoPath: string | Uri, - search: SearchPattern, + search: SearchQuery, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, ): Promise { const { provider, path } = this.getProvider(repoPath); diff --git a/src/git/models/graph.ts b/src/git/models/graph.ts index 33e888d..6d86c15 100644 --- a/src/git/models/graph.ts +++ b/src/git/models/graph.ts @@ -32,7 +32,7 @@ export interface GitGraph { readonly paging?: { readonly limit: number | undefined; readonly startingCursor: string | undefined; - readonly more: boolean; + readonly hasMore: boolean; }; more?(limit: number, sha?: string): Promise; diff --git a/src/git/models/repository.ts b/src/git/models/repository.ts index b89aef8..716a0ef 100644 --- a/src/git/models/repository.ts +++ b/src/git/models/repository.ts @@ -23,7 +23,7 @@ import type { GitProviderDescriptor } from '../gitProvider'; import { loadRemoteProviders } from '../remotes/remoteProviders'; import type { RemoteProviders } from '../remotes/remoteProviders'; 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 { getBranchNameWithoutRemote, getRemoteNameFromBranchName } from './branch'; import type { GitCommit } from './commit'; @@ -853,14 +853,14 @@ export class Repository implements Disposable { } searchForCommits( - search: SearchPattern, + search: SearchQuery, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo'; skip?: number }, ): Promise { return this.container.git.getLogForSearch(this.path, search, options); } searchForCommitsSimple( - search: SearchPattern, + search: SearchQuery, options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, ): Promise { return this.container.git.searchForCommitsSimple(this.path, search, options); diff --git a/src/git/search.ts b/src/git/search.ts index 3da1a61..fc5213d 100644 --- a/src/git/search.ts +++ b/src/git/search.ts @@ -28,7 +28,15 @@ export const searchOperators = new Set([ '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; matchAll?: boolean; matchCase?: boolean; @@ -37,34 +45,51 @@ export interface SearchPattern { export interface GitSearch { repoPath: string; - pattern: SearchPattern; + query: SearchQuery; comparisonKey: string; results: Set; readonly paging?: { readonly limit: number | undefined; - readonly startingCursor: string | undefined; - readonly more: boolean; + readonly hasMore: boolean; }; more?(limit: number): Promise; } -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}`; } -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(' '); } @@ -85,7 +110,7 @@ const normalizeSearchOperatorsMap = new Map([ const searchOperationRegex = /(?:(?=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)\s?(?".+?"|\S+\b}?))|(?\S+)(?!(?:=|message|@|author|#|commit|\?|file|~|change):)/gi; -export function parseSearchOperations(search: string): Map { +export function parseSearchQuery(query: string): Map { const operations = new Map(); let op: SearchOperators | undefined; @@ -94,7 +119,7 @@ export function parseSearchOperations(search: string): Map { let match; do { - match = searchOperationRegex.exec(search); + match = searchOperationRegex.exec(query); if (match?.groups == null) break; op = normalizeSearchOperatorsMap.get(match.groups.op as SearchOperators); @@ -117,3 +142,78 @@ export function parseSearchOperations(search: string): Map { return operations; } + +const doubleQuoteRegex = /"/g; + +export function getGitArgsFromSearchQuery(search: SearchQuery): { + args: string[]; + files: string[]; + shas?: Set | undefined; +} { + const operations = parseSearchQuery(search.query); + + const searchArgs = new Set(); + 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 }; +} diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts index 6d26705..cac918a 100644 --- a/src/plus/github/githubGitProvider.ts +++ b/src/plus/github/githubGitProvider.ts @@ -73,8 +73,8 @@ import type { RemoteProvider } from '../../git/remotes/remoteProvider'; import type { RemoteProviders } from '../../git/remotes/remoteProviders'; import { getRemoteProviderMatcher, loadRemoteProviders } from '../../git/remotes/remoteProviders'; 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 { Logger } from '../../logger'; import { gate } from '../../system/decorators/gate'; @@ -1215,7 +1215,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { limit: log.limit, // endingCursor: log.endingCursor, startingCursor: log.startingCursor, - more: log.hasMore, + hasMore: log.hasMore, }, more: async (limit: number | { until: string } | undefined): Promise => { const moreLog = await log.more?.(limit); @@ -1584,15 +1584,15 @@ export class GitHubGitProvider implements GitProvider, Disposable { @log() async searchForCommitsSimple( repoPath: string, - search: SearchPattern, + search: SearchQuery, _options?: { cancellation?: CancellationToken; limit?: number; ordering?: 'date' | 'author-date' | 'topo' }, ): Promise { search = { matchAll: false, matchCase: false, matchRegex: true, ...search }; - const comparisonKey = getSearchPatternComparisonKey(search); + const comparisonKey = getSearchQueryComparisonKey(search); return { repoPath: repoPath, - pattern: search, + query: search, comparisonKey: comparisonKey, results: new Set(), }; @@ -1685,14 +1685,14 @@ export class GitHubGitProvider implements GitProvider, Disposable { @log() async getLogForSearch( repoPath: string, - search: SearchPattern, + search: SearchQuery, options?: { cursor?: string; limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, ): Promise { if (repoPath == null) return undefined; const scope = getLogScope(); - const operations = parseSearchOperations(search.pattern); + const operations = parseSearchQuery(search.query); let op; let values = operations.get('commit:'); @@ -1838,7 +1838,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { private getLogForSearchMoreFn( log: GitLog, - search: SearchPattern, + search: SearchQuery, options?: { limit?: number; ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number }, ): (limit: number | undefined) => Promise { return async (limit: number | undefined) => { diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 0d05422..bcbca7a 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -14,7 +14,7 @@ import type { GitGraph } from '../../../git/models/graph'; import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository'; import { RepositoryChange, RepositoryChangeComparisonMode } from '../../../git/models/repository'; import type { GitSearch } from '../../../git/search'; -import { getSearchPatternComparisonKey } from '../../../git/search'; +import { getSearchQueryComparisonKey } from '../../../git/search'; import { registerCommand } from '../../../system/command'; import { gate } from '../../../system/decorators/gate'; import { debug } from '../../../system/decorators/log'; @@ -432,9 +432,8 @@ export class GraphWebview extends WebviewBase { private async onSearchCommits(e: SearchCommitsParams, completionId?: string) { 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) { this._search = search; @@ -443,10 +442,7 @@ export class GraphWebview extends WebviewBase { { results: { ids: [...search.results.values()], - paging: { - startingCursor: search.paging?.startingCursor, - more: search.paging?.more ?? false, - }, + paging: { hasMore: search.paging?.hasMore ?? false }, }, selectedRows: this._selectedRows, }, @@ -457,7 +453,7 @@ export class GraphWebview extends WebviewBase { 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.etag !== this._etagRepository) { @@ -499,10 +495,7 @@ export class GraphWebview extends WebviewBase { { results: { ids: [...search.results.values()], - paging: { - startingCursor: search.paging?.startingCursor, - more: search.paging?.more ?? false, - }, + paging: { hasMore: search.paging?.hasMore ?? false }, }, selectedRows: this._selectedRows, }, @@ -636,7 +629,7 @@ export class GraphWebview extends WebviewBase { selectedRows: this._selectedRows, paging: { startingCursor: data.paging?.startingCursor, - more: data.paging?.more ?? false, + hasMore: data.paging?.hasMore ?? false, }, }, completionId, @@ -729,7 +722,7 @@ export class GraphWebview extends WebviewBase { data != null ? { startingCursor: data.paging?.startingCursor, - more: data.paging?.more ?? false, + hasMore: data.paging?.hasMore ?? false, } : undefined, config: this.getComponentConfig(), diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index 2880de4..8e15119 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -2,7 +2,7 @@ import type { GraphRow, Remote } from '@gitkraken/gitkraken-components'; import type { DateStyle, GraphColumnConfig } from '../../../config'; import type { RepositoryVisibility } from '../../../git/gitProvider'; 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 { DateTimeFormat } from '../../../system/date'; import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol'; @@ -30,7 +30,7 @@ export interface State { export interface GraphPaging { startingCursor?: string; - more: boolean; + hasMore: boolean; } export interface GraphRepository { @@ -96,9 +96,9 @@ export interface GetMoreCommitsParams { export const GetMoreCommitsCommandType = new IpcCommandType('graph/getMoreCommits'); export interface SearchCommitsParams { - search: SearchPattern; - - more?: boolean | { limit?: number }; + search: SearchQuery; + limit?: number; + more?: boolean; } export const SearchCommitsCommandType = new IpcCommandType('graph/searchCommits'); @@ -174,12 +174,7 @@ export const DidEnsureCommitNotificationType = new IpcNotificationType( diff --git a/src/storage.ts b/src/storage.ts index 262872d..e3663f3 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -1,7 +1,7 @@ import type { Disposable, Event, ExtensionContext, SecretStorageChangeEvent } from 'vscode'; import { EventEmitter } from 'vscode'; 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 { TrackedUsage, TrackedUsageKeys } from './usageTracker'; import type { CompletedActions } from './webviews/home/protocol'; @@ -239,7 +239,7 @@ export interface StoredPinnedSearch { resultsType?: { singular: string; plural: string }; }; }; - search: SearchPattern; + search: StoredSearchQuery; } export type StoredPinnedItem = StoredPinnedComparison | StoredPinnedSearch; diff --git a/src/views/nodes/searchResultsNode.ts b/src/views/nodes/searchResultsNode.ts index dea2344..1227ad8 100644 --- a/src/views/nodes/searchResultsNode.ts +++ b/src/views/nodes/searchResultsNode.ts @@ -3,8 +3,8 @@ import { ThemeIcon } from 'vscode'; import { executeGitCommand } from '../../commands/gitCommands.actions'; import { GitUri } from '../../git/gitUri'; 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 { debug, log } from '../../system/decorators/log'; import { md5, pluralize } from '../../system/string'; @@ -26,14 +26,14 @@ interface SearchQueryResults { export class SearchResultsNode extends ViewNode implements PageableViewNode { 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}(${ - search == null ? '?' : getSearchPatternComparisonKey(search) + search == null ? '?' : getSearchQueryComparisonKey(search) }):${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; @@ -41,7 +41,7 @@ export class SearchResultsNode extends ViewNode implements view: SearchAndCompareView, parent: ViewNode, public readonly repoPath: string, - search: SearchPattern, + search: SearchQuery, private _labels: { label: string; queryLabel: @@ -83,8 +83,8 @@ export class SearchResultsNode extends ViewNode implements return this._pinned !== 0; } - private _search: SearchPattern; - get search(): SearchPattern { + private _search: SearchQuery; + get search(): SearchQuery { return this._search; } @@ -153,7 +153,7 @@ export class SearchResultsNode extends ViewNode implements } async edit(search?: { - pattern: SearchPattern; + pattern: SearchQuery; labels: { label: string; queryLabel: @@ -296,7 +296,7 @@ export class SearchResultsNode extends ViewNode implements timestamp: this._pinned, path: this.repoPath, labels: this._labels, - search: this.search, + search: getStoredSearchQuery(this.search), }); } } diff --git a/src/views/searchAndCompareView.ts b/src/views/searchAndCompareView.ts index 20603da..79a95de 100644 --- a/src/views/searchAndCompareView.ts +++ b/src/views/searchAndCompareView.ts @@ -8,7 +8,8 @@ import { setContext } from '../context'; import { unknownGitUri } from '../git/gitUri'; import type { GitLog } from '../git/models/log'; 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 { RepositoryPicker } from '../quickpicks/repositoryPicker'; import type { StoredNamedRef, StoredPinnedItem, StoredPinnedItems } from '../storage'; @@ -377,7 +378,7 @@ export class SearchAndCompareView extends ViewBase void; onMissingAvatars?: (emails: { [email: string]: string }) => void; onMoreCommits?: (id?: string) => void; - onSearchCommits?: (search: SearchPattern | undefined) => void; + onSearchCommits?: (search: SearchQuery | undefined, options?: { limit?: number }) => void; onSearchCommitsPromise?: ( - search: SearchPattern, - options?: { more?: boolean | { limit?: number } }, + search: SearchQuery, + options?: { limit?: number; more?: boolean }, ) => Promise; onDismissBanner?: (key: DismissBannerParams['key']) => void; onSelectionChange?: (selection: { id: string; type: GitGraphRowType }[]) => void; @@ -248,10 +248,10 @@ export function GraphWrapper({ // column setting UI const [columnSettingsExpanded, setColumnSettingsExpanded] = useState(false); // search state - const [search, setSearch] = useState(undefined); + const [searchQuery, setSearchQuery] = useState(undefined); const [searchResultKey, setSearchResultKey] = useState(undefined); const [searchResultIds, setSearchResultIds] = useState(searchResults?.ids); - const [hasMoreSearchResults, setHasMoreSearchResults] = useState(searchResults?.paging?.more ?? false); + const [hasMoreSearchResults, setHasMoreSearchResults] = useState(searchResults?.paging?.hasMore ?? false); useEffect(() => { if (graphRows.length === 0) { @@ -289,8 +289,8 @@ export function GraphWrapper({ if (next) { if (rowIndex < resultIds.length - 1) { 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 (resultIds.length < results.results.ids.length) { resultIds = results.results.ids; @@ -307,8 +307,8 @@ export function GraphWrapper({ } else if (rowIndex > 0) { rowIndex--; } 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 (resultIds.length < results.results.ids.length) { resultIds = results.results.ids; @@ -347,11 +347,11 @@ export function GraphWrapper({ } }; - const handleSearchInput = (e: CustomEvent) => { + const handleSearchInput = (e: CustomEvent) => { const detail = e.detail; - setSearch(detail); + setSearchQuery(detail); - const isValid = detail.pattern.length >= 3; + const isValid = detail.query.length >= 3; if (!isValid) { setSearchResultKey(undefined); setSearchResultIds(undefined); @@ -394,7 +394,7 @@ export function GraphWrapper({ setIsPrivateRepo(state.selectedRepositoryVisibility === RepositoryVisibility.Private); setIsLoading(state.loading); setSearchResultIds(state.searchResults?.ids); - setHasMoreSearchResults(state.searchResults?.paging?.more ?? false); + setHasMoreSearchResults(state.searchResults?.paging?.hasMore ?? false); } useEffect(() => subscriber?.(transformData), []); @@ -621,8 +621,8 @@ export function GraphWrapper({
handleSearchInput(e as CustomEvent)} + value={searchQuery?.query} + onChange={e => handleSearchInput(e as CustomEvent)} onPrevious={() => handleSearchNavigation(false)} onNext={() => handleSearchNavigation(true)} /> @@ -630,7 +630,7 @@ export function GraphWrapper({ aria-label="Graph search navigation" step={searchPosition} total={searchResultIds?.length ?? 0} - valid={Boolean(search?.pattern && search.pattern.length > 2)} + valid={Boolean(searchQuery?.query && searchQuery.query.length > 2)} more={hasMoreSearchResults} onPrevious={() => handleSearchNavigation(false)} onNext={() => handleSearchNavigation(true)} @@ -656,7 +656,7 @@ export function GraphWrapper({ formatCommitDateTime={getGraphDateFormatter(graphConfig)} getExternalIcon={getIconElementLibrary} graphRows={graphRows} - hasMoreCommits={pagingState?.more} + hasMoreCommits={pagingState?.hasMore} height={mainHeight} highlightedShas={searchHighlights} // highlightRowssOnRefHover={graphConfig?.highlightRowsOnRefHover} diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index ee3d153..52bca26 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import type { GraphColumnConfig } from '../../../../config'; import type { GitGraphRowType } from '../../../../git/models/graph'; -import type { SearchPattern } from '../../../../git/search'; +import type { SearchQuery } from '../../../../git/search'; import type { DismissBannerParams, GraphRepository, @@ -299,18 +299,18 @@ export class GraphApp extends App { return this.sendCommand(GetMoreCommitsCommandType, { sha: sha }); } - private onSearchCommits(search: SearchPattern | undefined) { + private onSearchCommits(search: SearchQuery | undefined, options?: { limit?: number }) { if (search == null) { this.state.searchResults = undefined; 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( SearchCommitsCommandType, - { search: search, more: options?.more }, + { search: search, limit: options?.limit, more: options?.more }, DidSearchCommitsNotificationType, ); } diff --git a/src/webviews/apps/shared/components/search/search-field.ts b/src/webviews/apps/shared/components/search/search-field.ts index 6e84ef1..f52611d 100644 --- a/src/webviews/apps/shared/components/search/search-field.ts +++ b/src/webviews/apps/shared/components/search/search-field.ts @@ -1,4 +1,5 @@ import { attr, css, customElement, FASTElement, html } from '@microsoft/fast-element'; +import type { SearchQuery } from '../../../../../git/search'; import '../codicon'; // match case is disabled unless regex is true @@ -191,11 +192,12 @@ export class SearchField extends FASTElement { } emitSearch() { - this.$emit('change', { - pattern: this.value, + const search: SearchQuery = { + query: this.value, matchAll: this.all, matchCase: this.case, matchRegex: this.regex, - }); + }; + this.$emit('change', search); } } diff --git a/src/webviews/rebase/rebaseEditor.ts b/src/webviews/rebase/rebaseEditor.ts index f1fb122..ad7cf65 100644 --- a/src/webviews/rebase/rebaseEditor.ts +++ b/src/webviews/rebase/rebaseEditor.ts @@ -495,7 +495,7 @@ export class RebaseEditorProvider implements CustomTextEditorProvider, Disposabl const html = content.replace( /#{(head|body|endOfBody|placement|cspSource|cspNonce|root|webroot)}/g, - (_substring, token) => { + (_substring: string, token: string) => { switch (token) { case 'endOfBody': return `