Skip to content

Commit

Permalink
omnibox: open results locally if possible (#6781)
Browse files Browse the repository at this point in the history
This modifies the behavior of clicking search results to link to local
files if the result represents a file in your local repo. With the
[recent changes](sourcegraph/sourcegraph#2943)
that heavily prefer results from your current repo, this should now be
most results.
  • Loading branch information
camdencheek authored Jan 24, 2025
1 parent 1049a20 commit 7ed51f2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 8 deletions.
59 changes: 52 additions & 7 deletions vscode/src/chat/chat-view/ChatController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { migrateAndNotifyForOutdatedModels } from '../../models/modelMigrator'
import { logDebug, outputChannelLogger } from '../../output-channel-logger'
import { hydratePromptText } from '../../prompts/prompt-hydration'
import { listPromptTags, mergedPromptsAndLegacyCommands } from '../../prompts/prompts'
import { workspaceFolderForRepo } from '../../repository/remoteRepos'
import { authProvider } from '../../services/AuthProvider'
import { AuthProviderSimplified } from '../../services/AuthProviderSimplified'
import { localStorage } from '../../services/LocalStorageProvider'
Expand Down Expand Up @@ -360,7 +361,7 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
})
break
case 'openRemoteFile':
this.openRemoteFile(message.uri)
this.openRemoteFile(message.uri, message.tryLocal ?? false)
break
case 'newFile':
await handleCodeFromSaveToNewFile(message.text, this.editor)
Expand Down Expand Up @@ -980,18 +981,23 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
return
}

private openRemoteFile(uri: vscode.Uri) {
const json = uri.toJSON()
const searchParams = (json.query || '').split('&')
private async openRemoteFile(uri: vscode.Uri, tryLocal?: boolean) {
if (tryLocal) {
try {
await this.openSourcegraphUriAsLocalFile(uri)
return
} catch {
// Ignore error, just continue to opening the remote file
}
}

const sourcegraphSchemaURI = vscode.Uri.from({
...json,
const sourcegraphSchemaURI = uri.with({
query: '',
scheme: 'codysourcegraph',
})

// Supported line params examples: L42 (single line) or L42-45 (line range)
const lineParam = searchParams.find((value: string) => value.match(/^L\d+(?:-\d+)?$/)?.length)
const lineParam = this.extractLineParamFromURI(uri)
const range = this.lineParamToRange(lineParam)

vscode.workspace.openTextDocument(sourcegraphSchemaURI).then(async doc => {
Expand All @@ -1001,6 +1007,45 @@ export class ChatController implements vscode.Disposable, vscode.WebviewViewProv
})
}

/**
* Attempts to open a Sourcegraph file URL as a local file in VS Code.
* Fails if the URI is not a valid Sourcegraph URL for a file or if the
* file does not belong to the current workspace.
*/
private async openSourcegraphUriAsLocalFile(uri: vscode.Uri): Promise<void> {
const match = uri.path.match(
/^\/*(?<repoName>[^@]*)(?<revision>@.*)?\/-\/blob\/(?<filePath>.*)$/
)
if (!match || !match.groups) {
throw new Error('failed to extract repo name and file path')
}
const { repoName, filePath } = match.groups

const workspaceFolder = await workspaceFolderForRepo(repoName)
if (!workspaceFolder) {
throw new Error('could not find workspace for repo')
}

const lineParam = this.extractLineParamFromURI(uri)
const selectionStart = this.lineParamToRange(lineParam).start
// Opening the file with an active selection is awkward, so use a zero-length
// selection to focus the target line without highlighting anything
const selection = new vscode.Range(selectionStart, selectionStart)

const fileUri = workspaceFolder.uri.with({
path: `${workspaceFolder.uri.path}/${filePath}`,
})
const document = await vscode.workspace.openTextDocument(fileUri)
await vscode.window.showTextDocument(document, {
selection,
preview: true,
})
}

private extractLineParamFromURI(uri: vscode.Uri): string | undefined {
return uri.query.split('&').find(key => key.match(/^L\d+(?:-\d+)?$/))
}

private lineParamToRange(lineParam?: string | null): vscode.Range {
const lines = (lineParam ?? '0')
.replace('L', '')
Expand Down
9 changes: 8 additions & 1 deletion vscode/src/chat/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@ export type WebviewMessage =
| { command: 'restoreHistory'; chatID: string }
| { command: 'links'; value: string }
| { command: 'openURI'; uri: Uri }
| { command: 'openRemoteFile'; uri: Uri }
| {
// Open a file from a Sourcegraph URL
command: 'openRemoteFile'
uri: Uri
// Attempt to open the same file locally if we can map
// the repository to an open workspace.
tryLocal?: boolean | undefined | null
}
| {
command: 'openFileLink'
uri: Uri
Expand Down
20 changes: 20 additions & 0 deletions vscode/src/repository/remoteRepos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import {
authStatus,
combineLatest,
debounceTime,
firstValueFrom,
fromVSCodeEvent,
graphqlClient,
isError,
type pendingOperation,
skipPendingOperation,
startWith,
switchMapReplayOperation,
} from '@sourcegraph/cody-shared'
Expand Down Expand Up @@ -89,3 +91,21 @@ export const remoteReposForAllWorkspaceFolders: Observable<
}
)
)

async function remoteReposForWorkspaceFolder(folder: vscode.WorkspaceFolder): Promise<string[]> {
return firstValueFrom(
repoNameResolver.getRepoNamesContainingUri(folder.uri).pipe(skipPendingOperation())
)
}

export async function workspaceFolderForRepo(
repoName: string
): Promise<vscode.WorkspaceFolder | undefined> {
for (const folder of vscode.workspace.workspaceFolders ?? []) {
const remoteRepos = await remoteReposForWorkspaceFolder(folder)
if (remoteRepos.some(remoteRepo => remoteRepo === repoName)) {
return folder
}
}
return undefined
}
1 change: 1 addition & 0 deletions vscode/webviews/components/codeSnippet/CodeSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const FileMatchSearchResult: FC<PropsWithChildren<FileMatchSearchResultPr
getVSCodeAPI().postMessage({
command: 'openRemoteFile',
uri,
tryLocal: true,
})
},
[fileURL, agentIDE, onSelect]
Expand Down

0 comments on commit 7ed51f2

Please sign in to comment.