Skip to content

Commit

Permalink
search: improved loading and typing behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
bobheadxi committed Dec 14, 2024
1 parent 5c831a4 commit 252f7c1
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
61 changes: 42 additions & 19 deletions src/components/SearchCommand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 = (
<Action.OpenInBrowser
icon={Icon.Window}
title="Continue query in browser"
url={getQueryURL(src, searchText, patternType)}
/>
);
const openSearchSyntaxAction = (
<Action.OpenInBrowser
icon={Icon.Book}
title="View search reference"
url={link.new(src, "/help/code_search/reference/queries")}
/>
);

return (
<List
isLoading={state.isLoading}
Expand All @@ -79,9 +96,24 @@ export default function SearchCommand({ src, props }: { src: Sourcegraph; props?
searchBarAccessory={
src.featureFlags.searchPatternDropdown ? <SearchDropdown setPatternType={setPatternType} /> : undefined
}
// actions are only shown when there aren't any children.
actions={
<ActionPanel>
{openQueryInBrowserAction}
{openSearchSyntaxAction}
{state.isLoading && (
<Action
icon={Icon.Xmark}
title="Cancel search"
onAction={() => setSearchText("")}
shortcut={deleteShortcut}
/>
)}
</ActionPanel>
}
>
{/* show suggestions IFF no results */}
{!state.isLoading && state.results.length === 0 ? (
{!state.isLoading && state.results.length === 0 && (
<List.Section title="Suggestions" subtitle={state.summary || ""}>
{state.suggestions.slice(0, 3).map((suggestion, i) => (
<SuggestionItem
Expand Down Expand Up @@ -113,32 +145,23 @@ export default function SearchCommand({ src, props }: { src: Sourcegraph; props?
<List.Item
title={`${searchText.length > 0 ? "Continue" : "Compose"} query in browser`}
icon={{ source: Icon.Window }}
actions={
<ActionPanel>
<Action.OpenInBrowser url={getQueryURL(src, searchText, patternType)} />
</ActionPanel>
}
actions={<ActionPanel>{openQueryInBrowserAction}</ActionPanel>}
/>
<List.Item
title="View search query syntax reference"
icon={{ source: Icon.QuestionMark }}
actions={
<ActionPanel>
<Action.OpenInBrowser url={link.new(src, "/help/code_search/reference/queries")} />
</ActionPanel>
}
icon={{ source: Icon.Book }}
actions={<ActionPanel>{openSearchSyntaxAction}</ActionPanel>}
/>
</Fragment>
</List.Section>
) : (
<Fragment />
)}

{state.isLoading && state.results.length === 0 && (
<List.EmptyView icon={Icon.MugSteam} title={"Searching..."} description={searchSummary} />
)}

{/* results */}
<List.Section
title="Results"
subtitle={state.summaryDetail ? `${state.summary} (${state.summaryDetail})` : state.summary}
>
<List.Section title="Results" subtitle={searchSummary}>
{state.results.map((searchResult, i) => (
<SearchResultItem
key={`result-item-${i}`}
Expand Down
44 changes: 32 additions & 12 deletions src/hooks/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export function useSearch(src: Sourcegraph, maxResults: number) {
async function search(searchText: string, pattern: PatternType) {
// Do not execute empty search - simply reset state
if (!searchText.trim()) {
cancelRef.current?.abort(); // reset state, cancel any active search
setState(emptyState);
return;
}
Expand All @@ -72,6 +73,10 @@ export function useSearch(src: Sourcegraph, maxResults: number) {
return;
}

// We're starting a new search - cancel the previous one
cancelRef.current?.abort();
cancelRef.current = new AbortController();

// Reset state for new search
setState({
...emptyState,
Expand All @@ -80,26 +85,39 @@ export function useSearch(src: Sourcegraph, maxResults: number) {
});

try {
// Cancel previous search
cancelRef.current?.abort();
cancelRef.current = new AbortController();

// Update search history
await SearchHistory.addSearch(src, searchText);

// Do the search
await performSearch(cancelRef.current.signal, src, searchText, pattern, {
onResults: (results) => {
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`,
};
});
},
Expand All @@ -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,
Expand All @@ -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,
}));
}
}
Expand Down
7 changes: 4 additions & 3 deletions src/sourcegraph/stream-search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export type PatternType =
| "nls";

export async function performSearch(
abort: AbortSignal,
abort: AbortController,
src: Sourcegraph,
query: string,
patternType: PatternType,
Expand All @@ -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 } = {
Expand All @@ -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) => {
Expand Down

0 comments on commit 252f7c1

Please sign in to comment.