|
|
@ -1,5 +1,6 @@ |
|
|
|
import { attr, css, customElement, FASTElement, html, observable, ref, volatile, when } from '@microsoft/fast-element'; |
|
|
|
import type { SearchQuery } from '../../../../../git/search'; |
|
|
|
import type { Deferrable } from '../../../../../system/function'; |
|
|
|
import { debounce } from '../../../../../system/function'; |
|
|
|
import '../code-icon'; |
|
|
|
import type { PopMenu } from '../overlays/pop-menu'; |
|
|
@ -18,7 +19,6 @@ export type SearchOperators = |
|
|
|
|
|
|
|
export type HelpTypes = 'message:' | 'author:' | 'commit:' | 'file:' | 'change:'; |
|
|
|
|
|
|
|
const searchRegex = /(?:^|(?<= ))(=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)/gi; |
|
|
|
const operatorsHelpMap = new Map<SearchOperators, HelpTypes>([ |
|
|
|
['=:', 'message:'], |
|
|
|
['message:', 'message:'], |
|
|
@ -479,29 +479,47 @@ export class SearchInput extends FASTElement { |
|
|
|
this.debouncedEmitSearch(); |
|
|
|
} |
|
|
|
|
|
|
|
private _updateHelpTextDebounced: Deferrable<SearchInput['updateHelpText']> | undefined; |
|
|
|
updateHelpText() { |
|
|
|
if (this.input == null || this.value === '' || !this.value.includes(':') || this.input.selectionStart == null) { |
|
|
|
this.helpType = undefined; |
|
|
|
return; |
|
|
|
if (this._updateHelpTextDebounced == null) { |
|
|
|
this._updateHelpTextDebounced = debounce(this.updateHelpTextCore.bind(this), 200); |
|
|
|
} |
|
|
|
|
|
|
|
const query = getSubstringFromCursor(this.value, this.input.selectionStart, this.input.selectionEnd); |
|
|
|
const helpOperator = query ? getHelpOperatorsFromQuery(query) : undefined; |
|
|
|
|
|
|
|
// console.log('updateHelpText operator', helpOperator, 'start', this.input.selectionStart, 'end', this.input.selectionEnd);
|
|
|
|
this.helpType = helpOperator; |
|
|
|
this._updateHelpTextDebounced(); |
|
|
|
} |
|
|
|
|
|
|
|
debouncedUpdateHelpText = debounce(this.updateHelpText.bind(this), 200); |
|
|
|
updateHelpTextCore() { |
|
|
|
const cursor = this.input?.selectionStart; |
|
|
|
const value = this.value; |
|
|
|
if (cursor != null && value.length !== 0 && !value.includes(':')) { |
|
|
|
const regex = |
|
|
|
/(?:^|[\b\s]*)((=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)(?:"[^"]*"?|\w*))(?:$|[\b\s])/gi; |
|
|
|
|
|
|
|
let match; |
|
|
|
do { |
|
|
|
match = regex.exec(value); |
|
|
|
if (match == null) break; |
|
|
|
|
|
|
|
const [, part, op] = match; |
|
|
|
|
|
|
|
console.log('updateHelpText', cursor, match.index, match.index + part.trim().length, match); |
|
|
|
if (cursor > match.index && cursor <= match.index + part.trim().length) { |
|
|
|
this.helpType = operatorsHelpMap.get(op as SearchOperators); |
|
|
|
return; |
|
|
|
} |
|
|
|
} while (true); |
|
|
|
} |
|
|
|
this.helpType = undefined; |
|
|
|
} |
|
|
|
|
|
|
|
handleInputClick(_e: MouseEvent) { |
|
|
|
this.debouncedUpdateHelpText(); |
|
|
|
this.updateHelpText(); |
|
|
|
} |
|
|
|
|
|
|
|
handleInput(e: InputEvent) { |
|
|
|
const value = (e.target as HTMLInputElement)?.value; |
|
|
|
this.value = value; |
|
|
|
this.debouncedUpdateHelpText(); |
|
|
|
this.updateHelpText(); |
|
|
|
this.debouncedEmitSearch(); |
|
|
|
} |
|
|
|
|
|
|
@ -521,7 +539,7 @@ export class SearchInput extends FASTElement { |
|
|
|
} |
|
|
|
|
|
|
|
handleKeyup(_e: KeyboardEvent) { |
|
|
|
this.debouncedUpdateHelpText(); |
|
|
|
this.updateHelpText(); |
|
|
|
} |
|
|
|
|
|
|
|
handleShortcutKeys(e: KeyboardEvent) { |
|
|
@ -542,7 +560,7 @@ export class SearchInput extends FASTElement { |
|
|
|
const value = this.searchHistory[nextPos]; |
|
|
|
if (value !== this.value) { |
|
|
|
this.value = value; |
|
|
|
this.debouncedUpdateHelpText(); |
|
|
|
this.updateHelpText(); |
|
|
|
this.debouncedEmitSearch(); |
|
|
|
} |
|
|
|
} |
|
|
@ -554,7 +572,7 @@ export class SearchInput extends FASTElement { |
|
|
|
handleInsertToken(token: string) { |
|
|
|
this.value += `${this.value.length > 0 ? ' ' : ''}${token}`; |
|
|
|
window.requestAnimationFrame(() => { |
|
|
|
this.debouncedUpdateHelpText(); |
|
|
|
this.updateHelpText(); |
|
|
|
// `@me` can be searched right away since it doesn't need additional text
|
|
|
|
if (token === '@me') { |
|
|
|
this.debouncedEmitSearch(); |
|
|
@ -592,39 +610,3 @@ export class SearchInput extends FASTElement { |
|
|
|
this.searchHistoryPos = this.searchHistory.length - 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function getSubstringFromCursor(value: string, start: number | null, end: number | null): string | undefined { |
|
|
|
if (value === '' || !value.includes(':') || start === null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const len = value.length; |
|
|
|
const cursor = end === null ? start : Math.max(start, end); |
|
|
|
if (cursor === len) { |
|
|
|
return value; |
|
|
|
} |
|
|
|
|
|
|
|
let query = cursor === 0 ? '' : value.substring(0, cursor); |
|
|
|
if (cursor < len - 1) { |
|
|
|
const next = value.charAt(cursor); |
|
|
|
if (next !== ' ') { |
|
|
|
// If the cursor is touching a word, include that word in the query
|
|
|
|
const match = /^[^\s]+/gi.exec(value.substring(cursor)); |
|
|
|
if (match !== null) { |
|
|
|
query += match[0]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return query; |
|
|
|
} |
|
|
|
|
|
|
|
function getHelpOperatorsFromQuery(value: string): HelpTypes | undefined { |
|
|
|
const matches = value.match(searchRegex); |
|
|
|
if (matches === null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const operator = operatorsHelpMap.get(matches.pop() as SearchOperators); |
|
|
|
return operator; |
|
|
|
} |