blob: e3887153ec6c1b91223e3e38dc06d2c0446cf957 [file] [log] [blame] [edit]
// 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,
};