From 252f7c113428a8e7cfeefa08e83dcbababc50d84 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 13 Dec 2024 17:01:03 -0800 Subject: [PATCH] search: improved loading and typing behaviour --- CHANGELOG.md | 1 + src/components/SearchCommand.tsx | 61 ++++++++++++++++++-------- src/hooks/search.tsx | 44 ++++++++++++++----- src/sourcegraph/stream-search/index.ts | 7 +-- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e034a..307a1a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - **search**: Opening a query in browser now correctly preserves the selected pattern type from the pattern type selector. +- **search**: Loading view is now improved. Typing during ongoing searches should also feel less jittery, as the suggestion view is now less likely to show up randomly. - **dotcom**: When using 'Public code on Sourcegraph.com' commands, if an access token is not provided a random anonymous user ID is now generated and used with API requests and telemetry for diagnostics. Telemetry can be disabled in the extension preferences. - **all**: 'Sourcegraph.com' commands are now called 'Public code on Sourcegraph.com', and 'Sourcegraph instance' commands are now just referred to as 'Sourcegraph' commands. diff --git a/src/components/SearchCommand.tsx b/src/components/SearchCommand.tsx index 5d14596..ec6f301 100644 --- a/src/components/SearchCommand.tsx +++ b/src/components/SearchCommand.tsx @@ -28,7 +28,7 @@ import { count, sentenceCase } from "../text"; import { useSearch } from "../hooks/search"; import { ColorDefault, ColorEmphasis, ColorError, ColorPrivate, ColorSubdued } from "./colors"; -import { copyShortcut, drilldownShortcut, tertiaryActionShortcut } from "./shortcuts"; +import { copyShortcut, drilldownShortcut, tertiaryActionShortcut, deleteShortcut } from "./shortcuts"; import { SearchHistory } from "../searchHistory"; import { useTelemetry } from "../hooks/telemetry"; import path from "path"; @@ -69,6 +69,23 @@ export default function SearchCommand({ src, props }: { src: Sourcegraph; props? }, [searchText, patternType]); const srcName = instanceName(src); + const searchSummary = state.summaryDetail ? `${state.summary} (${state.summaryDetail})` : state.summary; + + const openQueryInBrowserAction = ( + + ); + const openSearchSyntaxAction = ( + + ); + return ( : undefined } + // actions are only shown when there aren't any children. + actions={ + + {openQueryInBrowserAction} + {openSearchSyntaxAction} + {state.isLoading && ( + setSearchText("")} + shortcut={deleteShortcut} + /> + )} + + } > {/* show suggestions IFF no results */} - {!state.isLoading && state.results.length === 0 ? ( + {!state.isLoading && state.results.length === 0 && ( {state.suggestions.slice(0, 3).map((suggestion, i) => ( 0 ? "Continue" : "Compose"} query in browser`} icon={{ source: Icon.Window }} - actions={ - - - - } + actions={{openQueryInBrowserAction}} /> - - - } + icon={{ source: Icon.Book }} + actions={{openSearchSyntaxAction}} /> - ) : ( - + )} + + {state.isLoading && state.results.length === 0 && ( + )} {/* results */} - + {state.results.map((searchResult, i) => ( { + const noResultsTimeoutSeconds = 10; + setTimeout(() => { + if (state.results.length === 0) { setState((oldState) => { - if (oldState.results.length > maxResults) { + // Cancel search if there are no results yet after a timeout + if (oldState.results.length === 0) { + cancelRef.current?.abort(); return { ...oldState, - summaryDetail: `${maxResults} results shown`, + isLoading: false, + summary: `No results found in ${noResultsTimeoutSeconds} seconds`, }; } + return oldState; + }); + } + }, noResultsTimeoutSeconds * 1000); + + // Do the search + await performSearch(cancelRef.current, src, searchText, pattern, { + onResults: (results) => { + setState((oldState) => { + if (oldState.results.length > maxResults) { + return oldState; // do nothing + } + const newResults = oldState.results.concat(results); return { ...oldState, - results: oldState.results.concat(results), + results: newResults, + summaryDetail: `${newResults.length} results shown`, }; }); }, @@ -126,7 +144,8 @@ export function useSearch(src: Sourcegraph, maxResults: number) { .shiftTo(durationMs > 1000 ? "seconds" : "milliseconds") .toHuman({ unitDisplay: "narrow" }); - const results = `${Intl.NumberFormat().format(matchCount)}${skipped ? "+" : ""}`; + const results = + matchCount === 0 ? `${matchCount}` : `${Intl.NumberFormat().format(matchCount)}${skipped ? "+" : ""}`; setState((oldState) => ({ ...oldState, @@ -139,7 +158,8 @@ export function useSearch(src: Sourcegraph, maxResults: number) { } finally { setState((oldState) => ({ ...oldState, - isLoading: false, + // Another search may be in-flight already. + isLoading: !cancelRef.current?.signal.aborted, })); } } diff --git a/src/sourcegraph/stream-search/index.ts b/src/sourcegraph/stream-search/index.ts index 057ef0b..3f45630 100644 --- a/src/sourcegraph/stream-search/index.ts +++ b/src/sourcegraph/stream-search/index.ts @@ -51,7 +51,7 @@ export type PatternType = | "nls"; export async function performSearch( - abort: AbortSignal, + abort: AbortController, src: Sourcegraph, query: string, patternType: PatternType, @@ -67,7 +67,7 @@ export async function performSearch( ["q", query], ["v", LATEST_VERSION], ["t", patternType], - ["display", "200"], + ["display", "1500"], ]); const requestURL = link.new(src, "/.api/search/stream", parameters); const headers: { [key: string]: string } = { @@ -94,11 +94,12 @@ export async function performSearch( */ const resolveStream = () => { stream.close(); + abort.abort(); resolve(); }; // signal cancelling - abort.addEventListener("abort", resolveStream); + abort.signal.addEventListener("abort", resolveStream); // matches from the Sourcegraph API stream.addEventListener("matches", (message) => {