Skip to content
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
19 changes: 19 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ Each extension follows the standard VS Code extension structure with `package.js
3. **Follow imports**: Check what files import the problematic module
4. **Check test files**: Often reveal usage patterns and expected behavior

## Validating TypeScript changes

You MUST check compilation output before running ANY script or declaring work complete!

1. **ALWAYS** check the `VS Code - Build` watch task output for compilation errors
2. **NEVER** run tests if there are compilation errors
3. **NEVER** use `npm run compile` to compile TypeScript files, always check task output
4. **FIX** all compilation errors before moving forward

### TypeScript compilation steps
- Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes
- This task runs `Core - Build` and `Ext - Build` to incrementally compile VS Code TypeScript sources and built-in extensions
- Start the task if it's not already running in the background

### TypeScript validation steps
- Use run test tool or `scripts/test.sh` (`scripts\test.bat` on Windows) for unit tests (add `--grep <pattern>` to filter tests)
- Use `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests
- Use `npm run valid-layers-check` to check for layering issues

## Coding Guidelines

### Indentation
Expand Down
114 changes: 114 additions & 0 deletions .github/instructions/telemetry.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
applyTo: '**/*.ts'
description: Telemetry Implementation Guide
---

Patterns for GDPR-compliant telemetry in VS Code with proper type safety and privacy protection.

## Implementation Pattern

### 1. Define Types
```typescript
type MyFeatureEvent = {
action: string;
duration: number;
success: boolean;
errorCode?: string;
};

type MyFeatureClassification = {
action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action performed.' };
duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time in milliseconds.' };
success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether action succeeded.' };
errorCode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error code if action failed.' };
owner: 'yourGitHubUsername';
comment: 'Tracks MyFeature usage and performance.';
};
```

### 2.1. Send Event
```typescript
this.telemetryService.publicLog2<MyFeatureEvent, MyFeatureClassification>('myFeatureAction', {
action: 'buttonClick',
duration: 150,
success: true
});
```

### 2.2. Error Events
For error-specific telemetry with stack traces or error messages:
```typescript
type MyErrorEvent = {
operation: string;
errorMessage: string;
duration?: number;
};

type MyErrorClassification = {
operation: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The operation that failed.' };
errorMessage: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' };
duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time until failure.' };
owner: 'yourGitHubUsername';
comment: 'Tracks MyFeature errors for reliability.';
};

this.telemetryService.publicLogError2<MyErrorEvent, MyErrorClassification>('myFeatureError', {
operation: 'fileRead',
errorMessage: error.message,
duration: 1200
});
```

### 3. Service Injection
```typescript
constructor(
@ITelemetryService private readonly telemetryService: ITelemetryService,
) { super(); }
```

## GDPR Classifications & Purposes

**Classifications (choose the most restrictive):**
- `SystemMetaData` - **Most common.** Non-personal system info, user preferences, feature usage, identifiers (extension IDs, language types, counts, durations, success flags)
- `CallstackOrException` - Error messages, stack traces, exception details. **Only for actual error information.**
- `PublicNonPersonalData` - Data already publicly available (rare)

**Purposes (combine with different classifications):**
- `FeatureInsight` - **Default.** Understanding how features are used, user behavior patterns, feature adoption
- `PerformanceAndHealth` - **For errors & performance.** Metrics, error rates, performance measurements, diagnostics

**Required Properties:**
- `comment` - Clear explanation of what the field contains and why it's collected
- `owner` - GitHub username (infer from branch or ask)
- `isMeasurement: true` - **Required** for all numeric values flags used in calculations

## Error Events

Use `publicLogError2` for errors with `CallstackOrException` classification:

```typescript
this.telemetryService.publicLogError2<ErrorEvent, ErrorClassification>('myFeatureError', {
errorMessage: error.message,
errorCode: 'MYFEATURE_001',
context: 'initialization'
});
```

## Naming & Privacy Rules

**Naming Conventions:**
- Event names: `camelCase` with context (`extensionActivationError`, `chatMessageSent`)
- Property names: specific and descriptive (`agentId` not `id`, `durationMs` not `duration`)
- Common patterns: `success/hasError/isEnabled`, `sessionId/extensionId`, `type/kind/source`

**Critical Don'ts:**
- ❌ No PII (usernames, emails, file paths, content)
- ❌ Missing `owner` field in classification (infer from branch name or ask user)
- ❌ Vague comments ("user data" → "selected language identifier")
- ❌ Wrong classification
- ❌ Missing `isMeasurement` on numeric metrics

**Privacy Requirements:**
- Minimize data collection to essential insights only
- Use hashes/categories instead of raw values when possible
- Document clear purpose for each data point
25 changes: 0 additions & 25 deletions .github/instructions/typescript.instructions.md

This file was deleted.

5 changes: 5 additions & 0 deletions src/vs/base/browser/ui/hover/hoverWidget.css
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,18 @@

.monaco-hover .hover-row.status-bar .actions .action-container .action .icon {
padding-right: 4px;
vertical-align: middle;
}

.monaco-hover .hover-row.status-bar .actions .action-container a {
color: var(--vscode-textLink-foreground);
text-decoration: var(--text-link-decoration);
}

.monaco-hover .hover-row.status-bar .actions .action-container a .icon.codicon {
color: var(--vscode-textLink-foreground);
}

.monaco-hover .markdown-hover .hover-contents .codicon {
color: inherit;
font-size: inherit;
Expand Down
3 changes: 2 additions & 1 deletion src/vs/base/browser/ui/hover/hoverWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export class HoverAction extends Disposable {
this.action = dom.append(this.actionContainer, $('a.action'));
this.action.setAttribute('role', 'button');
if (actionOptions.iconClass) {
dom.append(this.action, $(`span.icon.${actionOptions.iconClass}`));
const iconElement = dom.append(this.action, $(`span.icon`));
iconElement.classList.add(...actionOptions.iconClass.split(' '));
}
this.actionRenderedLabel = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label;
const label = dom.append(this.action, $('span'));
Expand Down
3 changes: 3 additions & 0 deletions src/vs/editor/contrib/hover/browser/markerHoverParticipant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { ITextEditorOptions } from '../../../../platform/editor/common/editor.js
import { IMarker, IMarkerData, MarkerSeverity } from '../../../../platform/markers/common/markers.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { Progress } from '../../../../platform/progress/common/progress.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { Codicon } from '../../../../base/common/codicons.js';

const $ = dom.$;

Expand Down Expand Up @@ -264,6 +266,7 @@ export class MarkerHoverParticipant implements IEditorHoverParticipant<MarkerHov
context.statusBar.addAction({
label: aiCodeAction.action.title,
commandId: aiCodeAction.action.command?.id ?? '',
iconClass: ThemeIcon.asClassName(Codicon.sparkle),
run: () => {
const controller = CodeActionController.get(this._editor);
controller?.applyCodeAction(aiCodeAction, false, false, ApplyCodeActionReason.FromProblemsHover);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function registerQuickChatActions() {
title: localize2('chat.openInChatView.label', "Open in Chat View"),
f1: false,
category: CHAT_CATEGORY,
icon: Codicon.commentDiscussion,
icon: Codicon.chatSparkle,
menu: {
id: MenuId.ChatInputSide,
group: 'navigation',
Expand Down Expand Up @@ -71,7 +71,7 @@ class QuickChatGlobalAction extends Action2 {
id: ASK_QUICK_QUESTION_ACTION_ID,
title: localize2('quickChat', 'Quick Chat'),
precondition: ChatContextKeys.enabled,
icon: Codicon.commentDiscussion,
icon: Codicon.chatSparkle,
f1: false,
category: CHAT_CATEGORY,
keybinding: {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/contrib/chat/browser/chatEditorInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { ChatAgentLocation } from '../common/constants.js';
import { IClearEditingSessionConfirmationOptions } from './actions/chatActions.js';
import type { IChatEditorOptions } from './chatEditor.js';

const ChatEditorIcon = registerIcon('chat-editor-label-icon', Codicon.commentDiscussion, nls.localize('chatEditorLabelIcon', 'Icon of the chat editor label.'));
const ChatEditorIcon = registerIcon('chat-editor-label-icon', Codicon.chatSparkle, nls.localize('chatEditorLabelIcon', 'Icon of the chat editor label.'));

export class ChatEditorInput extends EditorInput implements IEditorCloseHandler {
static readonly countsInUse = new Set<number>();
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chatListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
private renderChatResponseBasic(element: IChatResponseViewModel, index: number, templateData: IChatListItemTemplate) {
templateData.rowContainer.classList.toggle('chat-response-loading', (isResponseVM(element) && !element.isComplete));

if (element.isCanceled && this._currentlyPinnedPart) {
this._finishedThinking = true;
this._currentlyPinnedPart.stopTimerAndFinalize();
this._currentlyPinnedPart = undefined;
}

const content: IChatRendererContent[] = [];
const isFiltered = !!element.errorDetails?.responseIsFiltered;
if (!isFiltered) {
Expand Down
9 changes: 9 additions & 0 deletions src/vs/workbench/contrib/chat/browser/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -2606,11 +2606,20 @@ have to be updated for changes to the rules above, or to support more deeply nes

.interactive-session .interactive-response .chat-used-context-list.chat-thinking-items {
color: var(--vscode-descriptionForeground);
padding-top: 6px;
}

.interactive-session .interactive-response .value .chat-thinking-box {
outline: none;

.chat-used-context {
margin: 0px;
}

.chat-thinking-item {
padding: 2px 0px;
}

.chat-thinking-text {
font-size: 12px;
padding: 3px 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ export class InlineEditArcTelemetrySender extends Disposable {
languageId: string;
didBranchChange: number;
timeDelayMs: number;
arc: number;

originalCharCount: number;
originalLineCount: number;
currentLineCount: number;
originalDeletedLineCount: number;
arc: number;
currentLineCount: number;
currentDeletedLineCount: number;
}, {
owner: 'hediet';
Expand All @@ -69,11 +70,12 @@ export class InlineEditArcTelemetrySender extends Disposable {

didBranchChange: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates if the branch changed in the meantime. If the branch changed (value is 1); this event should probably be ignored.' };
timeDelayMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The time delay between the user accepting the edit and measuring the survival rate.' };
arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' };

originalCharCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original character count before any edits.' };
originalLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original line count before any edits.' };
currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };
originalDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original deleted line count before any edits.' };
arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' };
currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };
currentDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current deleted line count after edits.' };
}>('editTelemetry.reportInlineEditArc', {
extensionId: data.$extensionId ?? '',
Expand All @@ -82,11 +84,12 @@ export class InlineEditArcTelemetrySender extends Disposable {
languageId: data.$$languageId,
didBranchChange: res.didBranchChange ? 1 : 0,
timeDelayMs: res.timeDelayMs,
arc: res.arc,

originalCharCount: res.originalCharCount,
originalLineCount: res.originalLineCount,
currentLineCount: res.currentLineCount,
originalDeletedLineCount: res.originalDeletedLineCount,
arc: res.arc,
currentLineCount: res.currentLineCount,
currentDeletedLineCount: res.currentDeletedLineCount,

...forwardToChannelIf(isCopilotLikeExtension(data.$extensionId)),
Expand Down Expand Up @@ -140,12 +143,13 @@ export class ChatArcTelemetrySender extends Disposable {

didBranchChange: number;
timeDelayMs: number;
arc: number;
originalCharCount: number;

originalCharCount: number;
originalLineCount: number;
currentLineCount: number;
originalDeletedLineCount: number;
arc: number;
currentLineCount: number;
currentDeletedLineCount: number;
}, {
owner: 'hediet';
comment: 'Reports the accepted and retained character count for an inline completion/edit.';
Expand All @@ -163,11 +167,13 @@ export class ChatArcTelemetrySender extends Disposable {

didBranchChange: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Indicates if the branch changed in the meantime. If the branch changed (value is 1); this event should probably be ignored.' };
timeDelayMs: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The time delay between the user accepting the edit and measuring the survival rate.' };
arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' };

originalCharCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original character count before any edits.' };
originalLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original line count before any edits.' };
currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };
originalDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The original deleted line count before any edits.' };
arc: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The accepted and restrained character count.' };
currentLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current line count after edits.' };
currentDeletedLineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The current deleted line count after edits.' };
}>('editTelemetry.reportEditArc', {
sourceKeyCleaned: data.toKey(Number.MAX_SAFE_INTEGER, {
$extensionId: false,
Expand All @@ -190,12 +196,13 @@ export class ChatArcTelemetrySender extends Disposable {

didBranchChange: res.didBranchChange ? 1 : 0,
timeDelayMs: res.timeDelayMs,
arc: res.arc,
originalCharCount: res.originalCharCount,

originalCharCount: res.originalCharCount,
originalLineCount: res.originalLineCount,
currentLineCount: res.currentLineCount,
originalDeletedLineCount: res.originalDeletedLineCount,
arc: res.arc,
currentLineCount: res.currentLineCount,
currentDeletedLineCount: res.currentDeletedLineCount,

...forwardToChannelIf(isCopilotLikeExtension(data.props.$extensionId)),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ export class ViewInChatAction extends AbstractInline1ChatAction {
super({
id: ACTION_VIEW_IN_CHAT,
title: localize('viewInChat', 'View in Chat'),
icon: Codicon.commentDiscussion,
icon: Codicon.chatSparkle,
precondition: CTX_INLINE_CHAT_VISIBLE,
menu: [{
id: MENU_INLINE_CHAT_WIDGET_STATUS,
Expand Down
4 changes: 0 additions & 4 deletions src/vs/workbench/contrib/scm/common/scm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,3 @@ export interface ISCMViewService {
readonly activeRepository: IObservable<ISCMRepository | undefined>;
pinActiveRepository(repository: ISCMRepository | undefined): void;
}

export const SCM_CHANGES_EDITOR_ID = 'workbench.editor.scmChangesEditor';

export interface ISCMChangesEditor { }
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ registerActiveXtermAction({
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
TerminalChatContextKeys.requestActive.negate(),
),
icon: Codicon.commentDiscussion,
icon: Codicon.chatSparkle,
menu: [{
id: MENU_TERMINAL_CHAT_WIDGET_STATUS,
group: 'zzz',
Expand Down
Loading