Skip to content

Commit 9257888

Browse files
chore(web): Scope code nav to current repository by default (#647)
1 parent 28986f4 commit 9257888

File tree

16 files changed

+396
-347
lines changed

16 files changed

+396
-347
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Added `ALWAYS_INDEX_FILE_PATTERNS` environment variable to allow specifying a comma seperated list of glob patterns matching file paths that should always be indexed, regardless of size or # of trigrams. [#631](https://github.com/sourcebot-dev/sourcebot/pull/631)
12+
- Added button to explore menu to toggle cross-repository search. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
1213

1314
### Fixed
1415
- Fixed issue where single quotes could not be used in search queries. [#629](https://github.com/sourcebot-dev/sourcebot/pull/629)
1516
- Fixed issue where files with special characters would fail to load. [#636](https://github.com/sourcebot-dev/sourcebot/issues/636)
1617
- Fixed Ask performance issues. [#632](https://github.com/sourcebot-dev/sourcebot/pull/632)
1718
- Fixed regression where creating a new Ask thread when unauthenticated would result in a 404. [#641](https://github.com/sourcebot-dev/sourcebot/pull/641)
1819

20+
### Changed
21+
- Changed the default behaviour for code nav to scope references & definitions search to the current repository. [#647](https://github.com/sourcebot-dev/sourcebot/pull/647)
22+
1923
## [4.10.0] - 2025-11-24
2024

2125
### Added

docs/docs/features/code-navigation.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import LicenseKeyRequired from '/snippets/license-key-required.mdx'
2121
| **Go to definition** | Clicking the "go to definition" button in the popover or clicking the symbol name navigates to the symbol's definition. |
2222
| **Find references** | Clicking the "find all references" button in the popover lists all references in the explore panel. |
2323
| **Explore panel** | Lists all references and definitions for the symbol selected in the popover. |
24+
| **Cross-repository navigation** | You can search across all repositories by clicking the globe icon in the explore panel. By default, references and definitions are scoped to the repository where the symbol is being resolved. |
2425

2526
## How does it work?
2627

packages/web/src/app/[domain]/browse/[...path]/components/pureCodePreviewPanel.tsx

Lines changed: 17 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,17 @@
33
import { ScrollArea } from "@/components/ui/scroll-area";
44
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
55
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
6-
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
76
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
87
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
98
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
109
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
1110
import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam";
1211
import { search } from "@codemirror/search";
1312
import CodeMirror, { EditorSelection, EditorView, ReactCodeMirrorRef, SelectionRange, ViewUpdate } from "@uiw/react-codemirror";
14-
import { useCallback, useEffect, useMemo, useState } from "react";
13+
import { useEffect, useMemo, useState } from "react";
1514
import { EditorContextMenu } from "../../../components/editorContextMenu";
16-
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
1715
import { BrowseHighlightRange, HIGHLIGHT_RANGE_QUERY_PARAM } from "../../hooks/utils";
18-
import { useBrowseState } from "../../hooks/useBrowseState";
1916
import { rangeHighlightingExtension } from "./rangeHighlightingExtension";
20-
import useCaptureEvent from "@/hooks/useCaptureEvent";
21-
import { createAuditAction } from "@/ee/features/audit/actions";
2217

2318
interface PureCodePreviewPanelProps {
2419
path: string;
@@ -40,9 +35,6 @@ export const PureCodePreviewPanel = ({
4035
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
4136
const keymapExtension = useKeymapExtension(editorRef?.view);
4237
const hasCodeNavEntitlement = useHasEntitlement("code-nav");
43-
const { updateBrowseState } = useBrowseState();
44-
const { navigateToPath } = useBrowseNavigation();
45-
const captureEvent = useCaptureEvent();
4638

4739
const highlightRangeQuery = useNonEmptyQueryParam(HIGHLIGHT_RANGE_QUERY_PARAM);
4840
const highlightRange = useMemo((): BrowseHighlightRange | undefined => {
@@ -88,7 +80,6 @@ export const PureCodePreviewPanel = ({
8880
}
8981
}
9082
}
91-
9283
}, [highlightRangeQuery]);
9384

9485
const extensions = useMemo(() => {
@@ -116,90 +107,31 @@ export const PureCodePreviewPanel = ({
116107

117108
// Scroll the highlighted range into view.
118109
useEffect(() => {
119-
if (!highlightRange || !editorRef || !editorRef.state) {
110+
if (!highlightRange || !editorRef || !editorRef.state || !editorRef.view) {
120111
return;
121112
}
122113

123114
const doc = editorRef.state.doc;
124115
const { start, end } = highlightRange;
125-
const selection = EditorSelection.range(
126-
doc.line(start.lineNumber).from,
127-
doc.line(end.lineNumber).from,
128-
);
129116

117+
const from = doc.line(start.lineNumber).from;
118+
const to = doc.line(end.lineNumber).to;
119+
const selection = EditorSelection.range(from, to);
120+
121+
// When the selection is in view, we don't want to perform any scrolling
122+
// as it could be jarring for the user. If it is not in view, scroll to the
123+
// center of the viewport.
124+
const viewport = editorRef.view.viewport;
125+
const isInView = from >= viewport.from && to <= viewport.to;
126+
const scrollStrategy = isInView ? "nearest" : "center";
127+
130128
editorRef.view?.dispatch({
131129
effects: [
132-
EditorView.scrollIntoView(selection, { y: "center" }),
130+
EditorView.scrollIntoView(selection, { y: scrollStrategy }),
133131
]
134132
});
135133
}, [editorRef, highlightRange]);
136134

137-
const onFindReferences = useCallback((symbolName: string) => {
138-
captureEvent('wa_find_references_pressed', {
139-
source: 'browse',
140-
});
141-
createAuditAction({
142-
action: "user.performed_find_references",
143-
metadata: {
144-
message: symbolName,
145-
},
146-
})
147-
148-
updateBrowseState({
149-
selectedSymbolInfo: {
150-
repoName,
151-
symbolName,
152-
revisionName,
153-
language,
154-
},
155-
isBottomPanelCollapsed: false,
156-
activeExploreMenuTab: "references",
157-
})
158-
}, [captureEvent, updateBrowseState, repoName, revisionName, language]);
159-
160-
161-
// If we resolve multiple matches, instead of navigating to the first match, we should
162-
// instead popup the bottom sheet with the list of matches.
163-
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
164-
captureEvent('wa_goto_definition_pressed', {
165-
source: 'browse',
166-
});
167-
createAuditAction({
168-
action: "user.performed_goto_definition",
169-
metadata: {
170-
message: symbolName,
171-
},
172-
})
173-
174-
if (symbolDefinitions.length === 0) {
175-
return;
176-
}
177-
178-
if (symbolDefinitions.length === 1) {
179-
const symbolDefinition = symbolDefinitions[0];
180-
const { fileName, repoName } = symbolDefinition;
181-
182-
navigateToPath({
183-
repoName,
184-
revisionName,
185-
path: fileName,
186-
pathType: 'blob',
187-
highlightRange: symbolDefinition.range,
188-
})
189-
} else {
190-
updateBrowseState({
191-
selectedSymbolInfo: {
192-
symbolName,
193-
repoName,
194-
revisionName,
195-
language,
196-
},
197-
activeExploreMenuTab: "definitions",
198-
isBottomPanelCollapsed: false,
199-
})
200-
}
201-
}, [captureEvent, navigateToPath, revisionName, updateBrowseState, repoName, language]);
202-
203135
const theme = useCodeMirrorTheme();
204136

205137
return (
@@ -223,11 +155,12 @@ export const PureCodePreviewPanel = ({
223155
)}
224156
{editorRef && hasCodeNavEntitlement && (
225157
<SymbolHoverPopup
158+
source="preview"
226159
editorRef={editorRef}
227160
revisionName={revisionName}
228161
language={language}
229-
onFindReferences={onFindReferences}
230-
onGotoDefinition={onGotoDefinition}
162+
fileName={path}
163+
repoName={repoName}
231164
/>
232165
)}
233166
</CodeMirror>

packages/web/src/app/[domain]/browse/layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { useBrowseParams } from "./hooks/useBrowseParams";
1010
import { FileSearchCommandDialog } from "./components/fileSearchCommandDialog";
1111
import { useDomain } from "@/hooks/useDomain";
1212
import { SearchBar } from "../components/searchBar";
13+
import escapeStringRegexp from "escape-string-regexp";
1314

1415
interface LayoutProps {
1516
children: React.ReactNode;
@@ -30,7 +31,7 @@ export default function Layout({
3031
<SearchBar
3132
size="sm"
3233
defaults={{
33-
query: `repo:${repoName}${revisionName ? ` rev:${revisionName}` : ''} `,
34+
query: `repo:^${escapeStringRegexp(repoName)}$${revisionName ? ` rev:${revisionName}` : ''} `,
3435
}}
3536
className="w-full"
3637
/>

packages/web/src/app/[domain]/search/components/codePreviewPanel/codePreview.tsx

Lines changed: 8 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
'use client';
22

3+
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
34
import { EditorContextMenu } from "@/app/[domain]/components/editorContextMenu";
45
import { Button } from "@/components/ui/button";
56
import { ScrollArea } from "@/components/ui/scroll-area";
7+
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
8+
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
9+
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
610
import { SearchResultChunk } from "@/features/search";
11+
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
712
import { useCodeMirrorTheme } from "@/hooks/useCodeMirrorTheme";
813
import { useKeymapExtension } from "@/hooks/useKeymapExtension";
9-
import { useCodeMirrorLanguageExtension } from "@/hooks/useCodeMirrorLanguageExtension";
1014
import { gutterWidthExtension } from "@/lib/extensions/gutterWidthExtension";
1115
import { highlightRanges, searchResultHighlightExtension } from "@/lib/extensions/searchResultHighlightExtension";
1216
import { search } from "@codemirror/search";
@@ -16,13 +20,6 @@ import { Scrollbar } from "@radix-ui/react-scroll-area";
1620
import CodeMirror, { ReactCodeMirrorRef, SelectionRange } from '@uiw/react-codemirror';
1721
import { ArrowDown, ArrowUp } from "lucide-react";
1822
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
19-
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
20-
import { SymbolHoverPopup } from "@/ee/features/codeNav/components/symbolHoverPopup";
21-
import { symbolHoverTargetsExtension } from "@/ee/features/codeNav/components/symbolHoverPopup/symbolHoverTargetsExtension";
22-
import { useHasEntitlement } from "@/features/entitlements/useHasEntitlement";
23-
import { SymbolDefinition } from "@/ee/features/codeNav/components/symbolHoverPopup/useHoveredOverSymbolInfo";
24-
import { createAuditAction } from "@/ee/features/audit/actions";
25-
import useCaptureEvent from "@/hooks/useCaptureEvent";
2623

2724
export interface CodePreviewFile {
2825
content: string;
@@ -59,8 +56,6 @@ export const CodePreview = ({
5956
const languageExtension = useCodeMirrorLanguageExtension(file?.language ?? '', editorRef?.view);
6057
const [currentSelection, setCurrentSelection] = useState<SelectionRange>();
6158

62-
const captureEvent = useCaptureEvent();
63-
6459
const extensions = useMemo(() => {
6560
return [
6661
keymapExtension,
@@ -115,81 +110,6 @@ export const CodePreview = ({
115110
onSelectedMatchIndexChange((prev) => prev + 1);
116111
}, [onSelectedMatchIndexChange]);
117112

118-
const onGotoDefinition = useCallback((symbolName: string, symbolDefinitions: SymbolDefinition[]) => {
119-
captureEvent('wa_goto_definition_pressed', {
120-
source: 'preview',
121-
});
122-
createAuditAction({
123-
action: "user.performed_goto_definition",
124-
metadata: {
125-
message: symbolName,
126-
},
127-
})
128-
129-
if (symbolDefinitions.length === 0) {
130-
return;
131-
}
132-
133-
if (symbolDefinitions.length === 1) {
134-
const symbolDefinition = symbolDefinitions[0];
135-
const { fileName, repoName } = symbolDefinition;
136-
137-
navigateToPath({
138-
repoName,
139-
revisionName: file.revision,
140-
path: fileName,
141-
pathType: 'blob',
142-
highlightRange: symbolDefinition.range,
143-
})
144-
} else {
145-
navigateToPath({
146-
repoName,
147-
revisionName: file.revision,
148-
path: file.filepath,
149-
pathType: 'blob',
150-
setBrowseState: {
151-
selectedSymbolInfo: {
152-
symbolName,
153-
repoName,
154-
revisionName: file.revision,
155-
language: file.language,
156-
},
157-
activeExploreMenuTab: "definitions",
158-
isBottomPanelCollapsed: false,
159-
}
160-
});
161-
}
162-
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]);
163-
164-
const onFindReferences = useCallback((symbolName: string) => {
165-
captureEvent('wa_find_references_pressed', {
166-
source: 'preview',
167-
});
168-
createAuditAction({
169-
action: "user.performed_find_references",
170-
metadata: {
171-
message: symbolName,
172-
},
173-
})
174-
175-
navigateToPath({
176-
repoName,
177-
revisionName: file.revision,
178-
path: file.filepath,
179-
pathType: 'blob',
180-
setBrowseState: {
181-
selectedSymbolInfo: {
182-
repoName,
183-
symbolName,
184-
revisionName: file.revision,
185-
language: file.language,
186-
},
187-
activeExploreMenuTab: "references",
188-
isBottomPanelCollapsed: false,
189-
}
190-
})
191-
}, [captureEvent, file.filepath, file.language, file.revision, navigateToPath, repoName]);
192-
193113
return (
194114
<div className="flex flex-col h-full">
195115
<div className="flex flex-row bg-accent items-center justify-between pr-3 py-0.5 mt-7">
@@ -286,11 +206,12 @@ export const CodePreview = ({
286206

287207
{editorRef && hasCodeNavEntitlement && (
288208
<SymbolHoverPopup
209+
source="preview"
289210
editorRef={editorRef}
290211
language={file.language}
291212
revisionName={file.revision}
292-
onFindReferences={onFindReferences}
293-
onGotoDefinition={onGotoDefinition}
213+
fileName={file.filepath}
214+
repoName={repoName}
294215
/>
295216
)}
296217
</CodeMirror>

packages/web/src/app/components/keyboardShortcutHint.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import { cn } from '@/lib/utils'
12
import React from 'react'
23

34
interface KeyboardShortcutHintProps {
45
shortcut: string
56
label?: string
7+
className?: string
68
}
79

8-
export function KeyboardShortcutHint({ shortcut, label }: KeyboardShortcutHintProps) {
10+
export function KeyboardShortcutHint({ shortcut, label, className }: KeyboardShortcutHintProps) {
911
return (
10-
<div className="inline-flex items-center" aria-label={label || `Keyboard shortcut: ${shortcut}`}>
12+
<div className={cn("inline-flex items-center", className)} aria-label={label || `Keyboard shortcut: ${shortcut}`}>
1113
<kbd
1214
className="px-2 py-1 font-semibold font-sans border rounded-md"
1315
style={{

packages/web/src/components/ui/toggle.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
77
import { cn } from "@/lib/utils"
88

99
const toggleVariants = cva(
10-
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2",
10+
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 gap-2 cursor-pointer",
1111
{
1212
variants: {
1313
variant: {
@@ -16,9 +16,7 @@ const toggleVariants = cva(
1616
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
1717
},
1818
size: {
19-
default: "h-10 px-3 min-w-10",
20-
sm: "h-9 px-2.5 min-w-9",
21-
lg: "h-11 px-5 min-w-11",
19+
default: "h-7 w-7 min-w-7 p-0",
2220
},
2321
},
2422
defaultVariants: {

0 commit comments

Comments
 (0)