mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-19 17:58:39 +00:00
Merge remote-tracking branch 'origin/main' into tyriar/276071_contrib_terminal_common__other
This commit is contained in:
@@ -338,24 +338,6 @@ export default tseslint.config(
|
||||
'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts',
|
||||
'src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts',
|
||||
'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/baseTerminalBackend.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/remotePty.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalActions.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalGroup.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalIcon.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalIconPicker.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalInstance.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalMenus.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalService.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/terminalView.ts',
|
||||
// 'src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts',
|
||||
'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts',
|
||||
'src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleBufferProvider.ts',
|
||||
'src/vs/workbench/contrib/terminalContrib/chat/browser/terminal.initialHint.contribution.ts',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions } from './git';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat } from './git';
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
|
||||
import { combinedDisposable, filterEvent, mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
@@ -163,6 +163,10 @@ export class ApiRepository implements Repository {
|
||||
return this.#repository.diffWithHEAD(path);
|
||||
}
|
||||
|
||||
diffWithHEADShortStats(path?: string): Promise<CommitShortStat> {
|
||||
return this.#repository.diffWithHEADShortStats(path);
|
||||
}
|
||||
|
||||
diffWith(ref: string): Promise<Change[]>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffWith(ref: string, path?: string): Promise<string | Change[]> {
|
||||
|
||||
1
extensions/git/src/api/git.d.ts
vendored
1
extensions/git/src/api/git.d.ts
vendored
@@ -237,6 +237,7 @@ export interface Repository {
|
||||
diff(cached?: boolean): Promise<string>;
|
||||
diffWithHEAD(): Promise<Change[]>;
|
||||
diffWithHEAD(path: string): Promise<string>;
|
||||
diffWithHEADShortStats(path?: string): Promise<CommitShortStat>;
|
||||
diffWith(ref: string): Promise<Change[]>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffIndexWithHEAD(): Promise<Change[]>;
|
||||
|
||||
@@ -1628,6 +1628,10 @@ export class Repository {
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
async diffWithHEADShortStats(path?: string): Promise<CommitShortStat> {
|
||||
return this.diffFilesShortStat(undefined, { cached: false, path });
|
||||
}
|
||||
|
||||
diffWith(ref: string): Promise<Change[]>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
|
||||
@@ -1717,6 +1721,32 @@ export class Repository {
|
||||
return parseGitChanges(this.repositoryRoot, gitResult.stdout);
|
||||
}
|
||||
|
||||
private async diffFilesShortStat(ref: string | undefined, options: { cached: boolean; path?: string }): Promise<CommitShortStat> {
|
||||
const args = ['diff', '--shortstat'];
|
||||
|
||||
if (options.cached) {
|
||||
args.push('--cached');
|
||||
}
|
||||
|
||||
if (ref !== undefined) {
|
||||
args.push(ref);
|
||||
}
|
||||
|
||||
args.push('--');
|
||||
|
||||
if (options.path) {
|
||||
args.push(this.sanitizeRelativePath(options.path));
|
||||
}
|
||||
|
||||
const result = await this.exec(args);
|
||||
if (result.exitCode) {
|
||||
return { files: 0, insertions: 0, deletions: 0 };
|
||||
}
|
||||
|
||||
return parseGitDiffShortStat(result.stdout.trim());
|
||||
}
|
||||
|
||||
|
||||
async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise<Change[]> {
|
||||
const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR'];
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Branch, BranchQuery, Change, CommitOptions, FetchOptions, ForcePushMode
|
||||
import { AutoFetcher } from './autofetch';
|
||||
import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection';
|
||||
import { debounce, memoize, sequentialize, throttle } from './decorators';
|
||||
import { Repository as BaseRepository, BlameInformation, Commit, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule, Worktree } from './git';
|
||||
import { Repository as BaseRepository, BlameInformation, Commit, CommitShortStat, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule, Worktree } from './git';
|
||||
import { GitHistoryProvider } from './historyProvider';
|
||||
import { Operation, OperationKind, OperationManager, OperationResult } from './operation';
|
||||
import { CommitCommandsCenter, IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
@@ -1207,6 +1207,10 @@ export class Repository implements Disposable {
|
||||
return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path));
|
||||
}
|
||||
|
||||
diffWithHEADShortStats(path?: string): Promise<CommitShortStat> {
|
||||
return this.run(Operation.Diff, () => this.repository.diffWithHEADShortStats(path));
|
||||
}
|
||||
|
||||
diffWith(ref: string): Promise<Change[]>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffWith(ref: string, path?: string | undefined): Promise<string | Change[]>;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { getAllCodicons } from '../../../base/common/codicons.js';
|
||||
import { Codicon, getAllCodicons } from '../../../base/common/codicons.js';
|
||||
import { IJSONSchema, IJSONSchemaMap } from '../../../base/common/jsonSchema.js';
|
||||
import { OperatingSystem, Platform, PlatformToString } from '../../../base/common/platform.js';
|
||||
import { localize } from '../../../nls.js';
|
||||
@@ -175,7 +175,7 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
default: {
|
||||
'PowerShell': {
|
||||
source: 'PowerShell',
|
||||
icon: 'terminal-powershell'
|
||||
icon: Codicon.terminalPowershell.id,
|
||||
},
|
||||
'Command Prompt': {
|
||||
path: [
|
||||
@@ -183,10 +183,11 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
'${env:windir}\\System32\\cmd.exe'
|
||||
],
|
||||
args: [],
|
||||
icon: 'terminal-cmd'
|
||||
icon: Codicon.terminalCmd,
|
||||
},
|
||||
'Git Bash': {
|
||||
source: 'Git Bash'
|
||||
source: 'Git Bash',
|
||||
icon: Codicon.terminalGitBash.id,
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
@@ -234,7 +235,7 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
'bash': {
|
||||
path: 'bash',
|
||||
args: ['-l'],
|
||||
icon: 'terminal-bash'
|
||||
icon: Codicon.terminalBash.id
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh',
|
||||
@@ -246,11 +247,11 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
icon: Codicon.terminalTmux.id
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
icon: Codicon.terminalPowershell.id
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
@@ -286,7 +287,7 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
default: {
|
||||
'bash': {
|
||||
path: 'bash',
|
||||
icon: 'terminal-bash'
|
||||
icon: Codicon.terminalBash.id
|
||||
},
|
||||
'zsh': {
|
||||
path: 'zsh'
|
||||
@@ -296,11 +297,11 @@ const terminalPlatformConfiguration: IConfigurationNode = {
|
||||
},
|
||||
'tmux': {
|
||||
path: 'tmux',
|
||||
icon: 'terminal-tmux'
|
||||
icon: Codicon.terminalTmux.id
|
||||
},
|
||||
'pwsh': {
|
||||
path: 'pwsh',
|
||||
icon: 'terminal-powershell'
|
||||
icon: Codicon.terminalPowershell.id
|
||||
}
|
||||
},
|
||||
additionalProperties: {
|
||||
|
||||
@@ -106,6 +106,7 @@ async function detectAvailableWindowsProfiles(
|
||||
});
|
||||
detectedProfiles.set('Git Bash', {
|
||||
source: ProfileSource.GitBash,
|
||||
icon: Codicon.terminalGitBash,
|
||||
isAutoDetected: true
|
||||
});
|
||||
detectedProfiles.set('Command Prompt', {
|
||||
|
||||
@@ -128,6 +128,9 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
|
||||
if (terminalCustomActions) {
|
||||
moreActions.push(...terminalCustomActions);
|
||||
}
|
||||
if (moreActions.length === 0) {
|
||||
moreActions = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const codeBlockRenderOptions: ICodeBlockRenderOptions = {
|
||||
|
||||
@@ -24,7 +24,18 @@ export interface ICommandLineAnalyzerOptions {
|
||||
}
|
||||
|
||||
export interface ICommandLineAnalyzerResult {
|
||||
/**
|
||||
* Whether auto approval is allowed based on the analysis, when false this
|
||||
* will block auto approval.
|
||||
*/
|
||||
readonly isAutoApproveAllowed: boolean;
|
||||
/**
|
||||
* Whether the command line was explicitly auto approved by this analyzer.
|
||||
* - `true`: This analyzer explicitly approves auto-execution
|
||||
* - `false`: This analyzer explicitly denies auto-execution
|
||||
* - `undefined`: This analyzer does not make an approval/denial decision
|
||||
*/
|
||||
readonly isAutoApproved?: boolean;
|
||||
readonly disclaimers?: readonly string[];
|
||||
readonly autoApproveInfo?: IMarkdownString;
|
||||
readonly customActions?: ToolConfirmationAction[];
|
||||
|
||||
@@ -155,7 +155,9 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma
|
||||
}
|
||||
|
||||
return {
|
||||
isAutoApproveAllowed: isAutoApproved,
|
||||
isAutoApproved,
|
||||
// This is not based on isDenied because we want the user to be able to configure it
|
||||
isAutoApproveAllowed: true,
|
||||
disclaimers,
|
||||
autoApproveInfo,
|
||||
customActions,
|
||||
|
||||
@@ -417,15 +417,29 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
|
||||
disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + disclaimersRaw.join(' '), { supportThemeIcons: true });
|
||||
}
|
||||
|
||||
const customActions = commandLineAnalyzerResults.map(e => e.customActions ?? []).flat();
|
||||
toolSpecificData.autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo;
|
||||
const analyzersIsAutoApproveAllowed = commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed);
|
||||
const customActions = analyzersIsAutoApproveAllowed ? commandLineAnalyzerResults.map(e => e.customActions ?? []).flat() : undefined;
|
||||
|
||||
let shellType = basename(shell, '.exe');
|
||||
if (shellType === 'powershell') {
|
||||
shellType = 'pwsh';
|
||||
}
|
||||
|
||||
const isFinalAutoApproved = isAutoApproveAllowed && commandLineAnalyzerResults.every(e => e.isAutoApproveAllowed);
|
||||
const isFinalAutoApproved = (
|
||||
// Is the setting enabled and the user has opted-in
|
||||
isAutoApproveAllowed &&
|
||||
// Does at least one analyzer auto approve
|
||||
commandLineAnalyzerResults.some(e => e.isAutoApproved) &&
|
||||
// No analyzer denies auto approval
|
||||
commandLineAnalyzerResults.every(e => e.isAutoApproved !== false) &&
|
||||
// All analyzers allow auto approval
|
||||
analyzersIsAutoApproveAllowed
|
||||
);
|
||||
|
||||
if (isFinalAutoApproved) {
|
||||
toolSpecificData.autoApproveInfo = commandLineAnalyzerResults.find(e => e.autoApproveInfo)?.autoApproveInfo;
|
||||
}
|
||||
|
||||
const confirmationMessages = isFinalAutoApproved ? undefined : {
|
||||
title: args.isBackground
|
||||
? localize('runInTerminal.background', "Run `{0}` command? (background terminal)", shellType)
|
||||
|
||||
@@ -35,6 +35,10 @@ import { arch } from '../../../../../../base/common/process.js';
|
||||
import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js';
|
||||
import type { SingleOrMany } from '../../../../../../base/common/types.js';
|
||||
import { IWorkspaceContextService, toWorkspaceFolder } from '../../../../../../platform/workspace/common/workspace.js';
|
||||
import { IHistoryService } from '../../../../../services/history/common/history.js';
|
||||
import { TestContextService } from '../../../../../test/common/workbenchTestServices.js';
|
||||
import { Workspace } from '../../../../../../platform/workspace/test/common/testWorkspace.js';
|
||||
|
||||
class TestRunInTerminalTool extends RunInTerminalTool {
|
||||
protected override _osBackend: Promise<OperatingSystem> = Promise.resolve(OperatingSystem.Windows);
|
||||
@@ -55,6 +59,7 @@ class TestRunInTerminalTool extends RunInTerminalTool {
|
||||
let configurationService: TestConfigurationService;
|
||||
let fileService: IFileService;
|
||||
let storageService: IStorageService;
|
||||
let workspaceContextService: TestContextService;
|
||||
let terminalServiceDisposeEmitter: Emitter<ITerminalInstance>;
|
||||
let chatServiceDisposeEmitter: Emitter<{ sessionResource: URI; reason: 'cleared' }>;
|
||||
|
||||
@@ -62,6 +67,7 @@ class TestRunInTerminalTool extends RunInTerminalTool {
|
||||
|
||||
setup(() => {
|
||||
configurationService = new TestConfigurationService();
|
||||
workspaceContextService = new TestContextService();
|
||||
|
||||
const logService = new NullLogService();
|
||||
fileService = store.add(new FileService(logService));
|
||||
@@ -77,6 +83,11 @@ class TestRunInTerminalTool extends RunInTerminalTool {
|
||||
fileService: () => fileService,
|
||||
}, store);
|
||||
|
||||
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
|
||||
instantiationService.stub(IHistoryService, {
|
||||
getLastActiveWorkspaceRoot: () => undefined
|
||||
});
|
||||
|
||||
const treeSitterLibraryService = store.add(instantiationService.createInstance(TreeSitterLibraryService));
|
||||
treeSitterLibraryService.isTest = true;
|
||||
instantiationService.stub(ITreeSitterLibraryService, treeSitterLibraryService);
|
||||
@@ -839,6 +850,25 @@ class TestRunInTerminalTool extends RunInTerminalTool {
|
||||
'configure',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should prevent auto approval when writing to a file outside the workspace', async () => {
|
||||
setConfig(TerminalChatAgentToolsSettingId.BlockDetectedFileWrites, 'outsideWorkspace');
|
||||
setAutoApprove({});
|
||||
|
||||
const workspaceFolder = URI.file(isWindows ? 'C:/workspace/project' : '/workspace/project');
|
||||
const workspace = new Workspace('test', [toWorkspaceFolder(workspaceFolder)]);
|
||||
workspaceContextService.setWorkspace(workspace);
|
||||
instantiationService.stub(IHistoryService, {
|
||||
getLastActiveWorkspaceRoot: () => workspaceFolder
|
||||
});
|
||||
|
||||
const result = await executeToolTest({
|
||||
command: 'echo "abc" > ../file.txt'
|
||||
});
|
||||
|
||||
assertConfirmationRequired(result);
|
||||
strictEqual(result?.confirmationMessages?.terminalCustomActions, undefined, 'Expected no custom actions when file write is blocked');
|
||||
});
|
||||
});
|
||||
|
||||
suite('chat session disposal cleanup', () => {
|
||||
|
||||
Reference in New Issue
Block a user