| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import * as path from 'path'; | |
| import * as vscode from 'vscode'; | |
| import {chromiumRoot} from '../common/chromium/fs'; | |
| import * as commonUtil from '../common/common_util'; | |
| import { | |
| vscodeRegisterCommand, | |
| vscodeRegisterTextEditorCommand, | |
| } from '../common/vscode/commands'; | |
| import * as ideUtil from '../ide_util'; | |
| import * as config from '../services/config'; | |
| import * as metrics from './metrics/metrics'; | |
| export function activate(context: vscode.ExtensionContext): void { | |
| const openFileCmd = vscodeRegisterTextEditorCommand( | |
| 'chromiumide.codeSearchOpenCurrentFile', | |
| (textEditor: vscode.TextEditor) => openCurrentFile(textEditor) | |
| ); | |
| // Used to open files from the explorer sidebar. | |
| const openFilesCmd = vscodeRegisterCommand( | |
| 'chromiumide.codeSearchOpen', | |
| // `clickedFile` corresponds to the file in the explorer sidebar that the right-click happened | |
| // on. `allSelectedFiles` contains all selected files, including `clickedFile`. | |
| (clickedFile: vscode.Uri, allSelectedFiles: vscode.Uri[]) => | |
| void openFiles(allSelectedFiles) | |
| ); | |
| const copyFileCmd = vscodeRegisterTextEditorCommand( | |
| 'chromiumide.codeSearchCopyCurrentFile', | |
| (textEditor: vscode.TextEditor) => copyCurrentFile(textEditor) | |
| ); | |
| const searchSelectionCmd = vscodeRegisterTextEditorCommand( | |
| 'chromiumide.codeSearchSearchForSelection', | |
| (textEditor: vscode.TextEditor) => searchSelection(textEditor) | |
| ); | |
| context.subscriptions.push( | |
| openFileCmd, | |
| openFilesCmd, | |
| searchSelectionCmd, | |
| copyFileCmd | |
| ); | |
| } | |
| function getCodeSearchToolConfig( | |
| fullpath: string | |
| ): {executable: string; cwd: string} | undefined { | |
| const chroot = commonUtil.findChroot(fullpath); | |
| if (!chroot) { | |
| return undefined; | |
| } | |
| const source = commonUtil.sourceDir(chroot); | |
| return { | |
| executable: path.join(source, 'chromite/contrib/generate_cs_path'), | |
| cwd: chroot, | |
| }; | |
| } | |
| async function openCurrentFile(textEditor: vscode.TextEditor): Promise<void> { | |
| const result = await getCurrentFile(textEditor); | |
| if (result) { | |
| void vscode.env.openExternal(vscode.Uri.parse(result)); | |
| metrics.send({ | |
| category: 'interactive', | |
| group: 'codesearch', | |
| name: 'codesearch_open_current_file', | |
| description: 'open current file', | |
| }); | |
| } | |
| } | |
| async function openFiles(allSelectedFiles: vscode.Uri[]): Promise<void> { | |
| const urls = await Promise.all( | |
| allSelectedFiles.map((uri: vscode.Uri) => | |
| getCodeSearchUrl(uri.fsPath, null) | |
| ) | |
| ); | |
| let opened = false; | |
| for (const url of urls) { | |
| if (url) { | |
| opened = true; | |
| void vscode.env.openExternal(vscode.Uri.parse(url)); | |
| } | |
| } | |
| if (opened) { | |
| metrics.Metrics.send({ | |
| category: 'interactive', | |
| group: 'codesearch', | |
| name: 'codesearch_open_files', | |
| description: 'open files from explorer sidebar', | |
| }); | |
| } | |
| } | |
| async function copyCurrentFile(textEditor: vscode.TextEditor): Promise<void> { | |
| const result = await getCurrentFile(textEditor); | |
| if (result) { | |
| void vscode.env.clipboard.writeText(result); | |
| metrics.send({ | |
| category: 'interactive', | |
| group: 'codesearch', | |
| name: 'codesearch_copy_current_file', | |
| description: 'copy current file', | |
| }); | |
| } | |
| } | |
| async function getCurrentFile( | |
| textEditor: vscode.TextEditor | |
| ): Promise<string | undefined> { | |
| const fullpath = textEditor.document.fileName; | |
| const line = textEditor.selection.active.line + 1; | |
| return getCodeSearchUrl(fullpath, line); | |
| } | |
| async function getCodeSearchUrl( | |
| fullpath: string, | |
| line: number | null | |
| ): Promise<string | undefined> { | |
| const chromium = await chromiumRoot(fullpath); | |
| if (chromium) { | |
| const relative = path.relative(chromium + '/src', fullpath); | |
| let url = | |
| 'https://source.chromium.org/chromium/chromium/src/+/main:' + | |
| encodeURI(relative); | |
| if (line !== null) { | |
| url += `;l=${line}`; | |
| } | |
| return url; | |
| } | |
| // Which CodeSearch to use, options are public, internal, or gitiles. | |
| const csInstance = config.codeSearch.instance.get(); | |
| const csHash = config.codeSearch.openWithRevision.get(); | |
| const csConfig = getCodeSearchToolConfig(fullpath); | |
| if (!csConfig) { | |
| void vscode.window.showErrorMessage( | |
| "Could not find 'generate_cs_path' script" | |
| ); | |
| return; | |
| } | |
| const {executable, cwd} = csConfig; | |
| const opts = []; | |
| if (csHash) { | |
| opts.push('--upstream-sha'); | |
| } | |
| opts.push('--show', `--${csInstance}`); | |
| if (line !== null) { | |
| opts.push(`--line=${line}`); | |
| } | |
| opts.push(fullpath); | |
| const res = await commonUtil.exec(executable, opts, { | |
| logger: ideUtil.getUiLogger(), | |
| logStdout: true, | |
| ignoreNonZeroExit: true, | |
| cwd: cwd, | |
| }); | |
| if (res instanceof Error) { | |
| void vscode.window.showErrorMessage( | |
| 'Could not run generate_cs_path: ' + res | |
| ); | |
| return; | |
| } | |
| const {exitStatus, stdout, stderr} = res; | |
| if (exitStatus) { | |
| void vscode.window.showErrorMessage( | |
| `generate_cs_path returned an error: ${stderr}` | |
| ); | |
| metrics.send({ | |
| category: 'error', | |
| group: 'codesearch', | |
| name: 'codesearch_generate_cs_path_failed', | |
| description: 'generate_cs_path failed', | |
| }); | |
| return; | |
| } | |
| // trimEnd() to get rid of the newline. | |
| return stdout.trimEnd(); | |
| } | |
| // TODO: Figure out if the search should be limited to the current repo. | |
| function searchSelection(textEditor: vscode.TextEditor): void { | |
| if (textEditor.selection.isEmpty) { | |
| return; | |
| } | |
| // If the setting is gitiles, we use public CodeSearch | |
| const csInstance = config.codeSearch.instance.get(); | |
| const csBase = | |
| csInstance === 'internal' | |
| ? 'https://source.corp.google.com/' | |
| : 'https://source.chromium.org/'; | |
| const selectedText = textEditor.document.getText(textEditor.selection); | |
| const uri = vscode.Uri.parse(csBase).with({ | |
| path: '/search', | |
| query: `q=${selectedText}`, | |
| }); | |
| void vscode.env.openExternal(uri); | |
| metrics.send({ | |
| category: 'interactive', | |
| group: 'codesearch', | |
| description: 'search selection', | |
| name: 'codesearch_search_selection', | |
| selected_text: selectedText, | |
| }); | |
| } | |
| export const TEST_ONLY = { | |
| openCurrentFile, | |
| openFiles, | |
| copyCurrentFile, | |
| searchSelection, | |
| }; |