Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pull] main from microsoft:main #392

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/notebooks/api.github-issues
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"November 2024\""
"value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"January 2025\""
},
{
"kind": 1,
Expand Down
11 changes: 3 additions & 8 deletions extensions/git/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,15 +349,10 @@ function getGitErrorCode(stderr: string): string | undefined {
return undefined;
}

// https://github.com/microsoft/vscode/issues/89373
// https://github.com/git-for-windows/git/issues/2478
function sanitizePath(path: string): string {
return path
// Drive letter
// https://github.com/microsoft/vscode/issues/89373
// https://github.com/git-for-windows/git/issues/2478
.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`)
// Shell-sensitive characters
// https://github.com/microsoft/vscode/issues/133566
.replace(/(["'\\\$!><#()\[\]*&^| ;{}?`])/g, '\\$1');
return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`);
}

const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B';
Expand Down
4 changes: 2 additions & 2 deletions src/typings/editContext.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ interface EditContextEventHandlersEventMap {

type EventHandler<TEvent extends Event = Event> = (event: TEvent) => void;

interface TextUpdateEvent extends Event {
new(type: DOMString, options?: TextUpdateEventInit): TextUpdateEvent;
declare class TextUpdateEvent extends Event {
constructor(type: DOMString, options?: TextUpdateEventInit);

readonly updateRangeStart: number;
readonly updateRangeEnd: number;
Expand Down
3 changes: 2 additions & 1 deletion src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js';
import * as nls from '../../../nls.js';
import { AccessibilitySupport } from '../../../platform/accessibility/common/accessibility.js';
import { IConfigurationPropertySchema } from '../../../platform/configuration/common/configurationRegistry.js';
import product from '../../../platform/product/common/product.js';

//#region typed options

Expand Down Expand Up @@ -5822,7 +5823,7 @@ export const EditorOptions = {
emptySelectionClipboard: register(new EditorEmptySelectionClipboard()),
dropIntoEditor: register(new EditorDropIntoEditor()),
experimentalEditContextEnabled: register(new EditorBooleanOption(
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', false,
EditorOption.experimentalEditContextEnabled, 'experimentalEditContextEnabled', product.quality !== 'stable',
{
description: nls.localize('experimentalEditContextEnabled', "Sets whether the new experimental edit context should be used instead of the text area."),
included: platform.isChrome || platform.isEdge || platform.isNative
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/common/core/editorColorRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const editorBracketHighlightingForeground4 = registerColor('editorBracket
export const editorBracketHighlightingForeground5 = registerColor('editorBracketHighlight.foreground5', '#00000000', nls.localize('editorBracketHighlightForeground5', 'Foreground color of brackets (5). Requires enabling bracket pair colorization.'));
export const editorBracketHighlightingForeground6 = registerColor('editorBracketHighlight.foreground6', '#00000000', nls.localize('editorBracketHighlightForeground6', 'Foreground color of brackets (6). Requires enabling bracket pair colorization.'));

export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hcDark: 'new Color(new RGBA(255, 50, 50, 1))', hcLight: '#B5200D' }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.'));
export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hcDark: new Color(new RGBA(255, 50, 50, 1)), hcLight: '#B5200D' }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.'));

export const editorBracketPairGuideBackground1 = registerColor('editorBracketPairGuide.background1', '#00000000', nls.localize('editorBracketPairGuide.background1', 'Background color of inactive bracket pair guides (1). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground2 = registerColor('editorBracketPairGuide.background2', '#00000000', nls.localize('editorBracketPairGuide.background2', 'Background color of inactive bracket pair guides (2). Requires enabling bracket pair guides.'));
Expand Down
4 changes: 2 additions & 2 deletions src/vs/workbench/api/common/extHost.api.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,10 +1500,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostLanguageModelTools.registerTool(extension, name, tool);
},
invokeTool<T>(name: string, parameters: vscode.LanguageModelToolInvocationOptions<T>, token?: vscode.CancellationToken) {
return extHostLanguageModelTools.invokeTool(name, parameters, token);
return extHostLanguageModelTools.invokeTool(extension, name, parameters, token);
},
get tools() {
return extHostLanguageModelTools.tools;
return extHostLanguageModelTools.getTools(extension);
},
fileIsIgnored(uri: vscode.Uri, token: vscode.CancellationToken) {
return extHostLanguageModels.fileIsIgnored(extension, uri, token);
Expand Down
27 changes: 20 additions & 7 deletions src/vs/workbench/api/common/extHostLanguageModelTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { IExtensionDescription } from '../../../platform/extensions/common/exten
import { IPreparedToolInvocation, isToolInvocationContext, IToolInvocation, IToolInvocationContext, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js';
import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js';
import * as typeConvert from './extHostTypeConverters.js';
import { isProposedApiEnabled } from '../../services/extensions/common/extensions.js';



Expand Down Expand Up @@ -45,17 +46,22 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
return await fn(input, token);
}

async invokeTool(toolId: string, options: vscode.LanguageModelToolInvocationOptions<any>, token?: CancellationToken): Promise<vscode.LanguageModelToolResult> {
async invokeTool(extension: IExtensionDescription, toolId: string, options: vscode.LanguageModelToolInvocationOptions<any>, token?: CancellationToken): Promise<vscode.LanguageModelToolResult> {
const callId = generateUuid();
if (options.tokenizationOptions) {
this._tokenCountFuncs.set(callId, options.tokenizationOptions.countTokens);
}

if (options.toolInvocationToken && !isToolInvocationContext(options.toolInvocationToken)) {
throw new Error(`Invalid tool invocation token`);
}

try {
if (options.toolInvocationToken && !isToolInvocationContext(options.toolInvocationToken)) {
throw new Error(`Invalid tool invocation token`);
}

const tool = this._allTools.get(toolId);
if (tool?.tags?.includes('vscode_editing') && !isProposedApiEnabled(extension, 'chatParticipantPrivate')) {
throw new Error(`Invalid tool: ${toolId}`);
}

// Making the round trip here because not all tools were necessarily registered in this EH
const result = await this._proxy.$invokeTool({
toolId,
Expand All @@ -77,9 +83,16 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape
}
}

get tools(): vscode.LanguageModelToolInformation[] {
getTools(extension: IExtensionDescription): vscode.LanguageModelToolInformation[] {
return Array.from(this._allTools.values())
.map(tool => typeConvert.LanguageModelToolDescription.to(tool));
.map(tool => typeConvert.LanguageModelToolDescription.to(tool))
.filter(tool => {
if (tool.tags.includes('vscode_editing')) {
return isProposedApiEnabled(extension, 'chatParticipantPrivate');
}

return true;
});
}

async $invokeTool(dto: IToolInvocation, token: CancellationToken): Promise<IToolResult> {
Expand Down
31 changes: 24 additions & 7 deletions src/vs/workbench/common/editor/textEditorModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,22 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
return true;
}

private _hasLanguageSetExplicitly: boolean = false;
get hasLanguageSetExplicitly(): boolean { return this._hasLanguageSetExplicitly; }
private _blockLanguageChangeListener = false;
private _languageChangeSource: 'user' | 'api' | undefined = undefined;
get languageChangeSource() { return this._languageChangeSource; }
get hasLanguageSetExplicitly() {
// This is technically not 100% correct, because 'api' can also be
// set as source if a model is resolved as text first and then
// transitions into the resolved language. But to preserve the current
// behaviour, we do not change this property. Rather, `languageChangeSource`
// can be used to get more fine grained information.
return typeof this._languageChangeSource === 'string';
}

setLanguageId(languageId: string, source?: string): void {

// Remember that an explicit language was set
this._hasLanguageSetExplicitly = true;
this._languageChangeSource = 'user';

this.setLanguageIdInternal(languageId, source);
}
Expand All @@ -95,18 +104,26 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
return;
}

this.textEditorModel.setLanguage(this.languageService.createById(languageId), source);
this._blockLanguageChangeListener = true;
try {
this.textEditorModel.setLanguage(this.languageService.createById(languageId), source);
} finally {
this._blockLanguageChangeListener = false;
}
}

protected installModelListeners(model: ITextModel): void {

// Setup listener for lower level language changes
const disposable = this._register(model.onDidChangeLanguage((e) => {
if (e.source === LanguageDetectionLanguageEventSource) {
const disposable = this._register(model.onDidChangeLanguage(e => {
if (
e.source === LanguageDetectionLanguageEventSource ||
this._blockLanguageChangeListener
) {
return;
}

this._hasLanguageSetExplicitly = true;
this._languageChangeSource = 'api';
disposable.dispose();
}));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import { Codicon } from '../../../../../base/common/codicons.js';
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
import { URI } from '../../../../../base/common/uri.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize, localize2 } from '../../../../../nls.js';
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js';
Expand Down Expand Up @@ -75,6 +75,52 @@ export class ChatSubmitAction extends SubmitAction {
}
}

export const ToggleAgentModeActionId = 'workbench.action.chat.toggleAgentMode';
export class ToggleAgentModeAction extends Action2 {
static readonly ID = ToggleAgentModeActionId;

constructor() {
super({
id: ToggleAgentModeAction.ID,
title: localize2('interactive.toggleAgent.label', "Toggle Agent Mode"),
f1: true,
category: CHAT_CATEGORY,
precondition: ContextKeyExpr.and(
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession),
ChatContextKeys.Editing.hasToolsAgent),
icon: Codicon.edit,
toggled: {
condition: ChatContextKeys.Editing.agentMode,
icon: Codicon.tools,
tooltip: localize('agentEnabled', "Agent Mode Enabled"),
},
tooltip: localize('agentDisabled', "Agent Mode Disabled"),
keybinding: {
when: ContextKeyExpr.and(
ChatContextKeys.inChatInput,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)),
primary: KeyMod.CtrlCmd | KeyCode.Period,
weight: KeybindingWeight.EditorContrib
},
menu: [
{
id: MenuId.ChatExecute,
order: 1,
when: ContextKeyExpr.and(
ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession),
ChatContextKeys.Editing.hasToolsAgent),
group: 'navigation',
},
]
});
}

override run(accessor: ServicesAccessor, ...args: any[]): void {
const agentService = accessor.get(IChatAgentService);
agentService.toggleToolsAgentMode();
}
}

export class ChatEditingSessionSubmitAction extends SubmitAction {
static readonly ID = 'workbench.action.edits.submit';

Expand Down Expand Up @@ -388,4 +434,5 @@ export function registerChatExecuteActions() {
registerAction2(SendToNewChatAction);
registerAction2(ChatSubmitSecondaryAgentAction);
registerAction2(SendToChatEditingAction);
registerAction2(ToggleAgentModeAction);
}
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { Extensions, IConfigurationMigrationRegistry } from '../../../common/con
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js';
import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js';
import { BuiltinToolsContribution } from './tools/tools.js';
import { ChatSetupContribution } from './chatSetup.js';

// Register configuration
Expand Down Expand Up @@ -319,6 +320,7 @@ registerWorkbenchContribution2(ChatViewsWelcomeHandler.ID, ChatViewsWelcomeHandl
registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingStartedContribution, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore);
registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually);
registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually);

registerChatActions();
registerChatCopyActions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,22 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP

// We release editors in order so that it's more likely that the same editor will be assigned if this element is re-rendered right away, like it often is during progressive rendering
const orderedDisposablesList: IDisposable[] = [];
let codeBlockIndex = codeBlockStartIndex;

// Need to track the index of the codeblock within the response so it can have a unique ID,
// and within this part to find it within the codeblocks array
let globalCodeBlockIndexStart = codeBlockStartIndex;
let thisPartCodeBlockIndexStart = 0;
const result = this._register(renderer.render(markdown.content, {
fillInIncompleteTokens,
codeBlockRendererSync: (languageId, text, raw) => {
const isCodeBlockComplete = !isResponseVM(context.element) || context.element.isComplete || !raw || raw?.endsWith('```');
const isCodeBlockComplete = !isResponseVM(context.element) || context.element.isComplete || !raw || raw?.trim().endsWith('```');
if ((!text || (text.startsWith('<vscode_codeblock_uri>') && !text.includes('\n'))) && !isCodeBlockComplete && rendererOptions.renderCodeBlockPills) {
const hideEmptyCodeblock = $('div');
hideEmptyCodeblock.style.display = 'none';
return hideEmptyCodeblock;
}
const index = codeBlockIndex++;
const globalIndex = globalCodeBlockIndexStart++;
const thisPartIndex = thisPartCodeBlockIndexStart++;
let textModel: Promise<ITextModel>;
let range: Range | undefined;
let vulns: readonly IMarkdownVulnerability[] | undefined;
Expand All @@ -101,15 +106,15 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
}
} else {
const sessionId = isResponseVM(element) || isRequestVM(element) ? element.sessionId : '';
const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, index);
const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, index, { text, languageId, isComplete: isCodeBlockComplete });
const modelEntry = this.codeBlockModelCollection.getOrCreate(sessionId, element, globalIndex);
const fastUpdateModelEntry = this.codeBlockModelCollection.updateSync(sessionId, element, globalIndex, { text, languageId, isComplete: isCodeBlockComplete });
vulns = modelEntry.vulns;
codemapperUri = fastUpdateModelEntry.codemapperUri;
textModel = modelEntry.model;
}

const hideToolbar = isResponseVM(element) && element.errorDetails?.responseIsFiltered;
const codeBlockInfo: ICodeBlockData = { languageId, textModel, codeBlockIndex: index, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri };
const codeBlockInfo: ICodeBlockData = { languageId, textModel, codeBlockIndex: globalIndex, codeBlockPartIndex: thisPartIndex, element, range, hideToolbar, parentContextKeyService: contextKeyService, vulns, codemapperUri };

if (!rendererOptions.renderCodeBlockPills || element.isCompleteAddedRequest || !codemapperUri) {
const ref = this.renderCodeBlock(codeBlockInfo, text, isCodeBlockComplete, currentWidth);
Expand All @@ -122,7 +127,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
const ownerMarkdownPartId = this.id;
const info: IChatCodeBlockInfo = new class {
readonly ownerMarkdownPartId = ownerMarkdownPartId;
readonly codeBlockIndex = index;
readonly codeBlockIndex = globalIndex;
readonly element = element;
readonly isStreaming = !rendererOptions.renderCodeBlockPills;
codemapperUri = undefined; // will be set async
Expand All @@ -149,15 +154,15 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
// TODO@joyceerhl: remove this code when we change the codeblockUri API to make the URI available synchronously
this.codeBlockModelCollection.update(codeBlockInfo.element.sessionId, codeBlockInfo.element, codeBlockInfo.codeBlockIndex, { text, languageId: codeBlockInfo.languageId, isComplete: isCodeBlockComplete }).then((e) => {
// Update the existing object's codemapperUri
this.codeblocks[codeBlockInfo.codeBlockIndex].codemapperUri = e.codemapperUri;
this.codeblocks[codeBlockInfo.codeBlockPartIndex].codemapperUri = e.codemapperUri;
this._onDidChangeHeight.fire();
});
}
this.allRefs.push(ref);
const ownerMarkdownPartId = this.id;
const info: IChatCodeBlockInfo = new class {
readonly ownerMarkdownPartId = ownerMarkdownPartId;
readonly codeBlockIndex = index;
readonly codeBlockIndex = globalIndex;
readonly element = element;
readonly isStreaming = !isCodeBlockComplete;
readonly codemapperUri = codemapperUri;
Expand Down Expand Up @@ -205,7 +210,7 @@ export class ChatMarkdownContentPart extends Disposable implements IChatContentP
if (isResponseVM(data.element)) {
this.codeBlockModelCollection.update(data.element.sessionId, data.element, data.codeBlockIndex, { text, languageId: data.languageId, isComplete }).then((e) => {
// Update the existing object's codemapperUri
this.codeblocks[data.codeBlockIndex].codemapperUri = e.codemapperUri;
this.codeblocks[data.codeBlockPartIndex].codemapperUri = e.codemapperUri;
this._onDidChangeHeight.fire();
});
}
Expand Down
Loading
Loading