mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'main' into fix-201081
This commit is contained in:
commit
2535c6ca13
|
@ -2961,8 +2961,10 @@
|
|||
"configurationDefaults": {
|
||||
"[git-commit]": {
|
||||
"editor.rulers": [
|
||||
50,
|
||||
72
|
||||
],
|
||||
"editor.wordWrap": "off",
|
||||
"workbench.editor.restoreViewState": false
|
||||
},
|
||||
"[git-rebase]": {
|
||||
|
|
|
@ -2543,7 +2543,7 @@ export class Repository {
|
|||
return branch;
|
||||
}
|
||||
|
||||
return Promise.reject<Branch>(new Error('No such branch'));
|
||||
return Promise.reject<Branch>(new Error(`No such branch: ${name}`));
|
||||
}
|
||||
|
||||
async getDefaultBranch(): Promise<Branch> {
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
{ "open": "(", "close": ")" },
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string"] },
|
||||
{ "open": "<!--", "close": "-->", "notIn": [ "comment", "string" ]},
|
||||
{ "open": "<![CDATA[", "close": "]]>", "notIn": [ "comment", "string" ]}
|
||||
{ "open": "<!--", "close": "-->", "notIn": [ "comment", "string" ]}
|
||||
],
|
||||
"surroundingPairs": [
|
||||
{ "open": "'", "close": "'" },
|
||||
|
|
|
@ -436,15 +436,17 @@ export function unmnemonicLabel(label: string): string {
|
|||
}
|
||||
|
||||
/**
|
||||
* Splits a recent label in name and parent path, supporting both '/' and '\' and workspace suffixes
|
||||
* Splits a recent label in name and parent path, supporting both '/' and '\' and workspace suffixes.
|
||||
* If the location is remote, the remote name is included in the name part.
|
||||
*/
|
||||
export function splitRecentLabel(recentLabel: string) {
|
||||
export function splitRecentLabel(recentLabel: string): { name: string; parentPath: string } {
|
||||
if (recentLabel.endsWith(']')) {
|
||||
// label with workspace suffix
|
||||
const lastIndexOfSquareBracket = recentLabel.lastIndexOf(' [', recentLabel.length - 2);
|
||||
if (lastIndexOfSquareBracket !== -1) {
|
||||
const split = splitName(recentLabel.substring(0, lastIndexOfSquareBracket));
|
||||
return { name: split.name, parentPath: split.parentPath + recentLabel.substring(lastIndexOfSquareBracket) };
|
||||
const remoteNameWithSpace = recentLabel.substring(lastIndexOfSquareBracket);
|
||||
return { name: split.name + remoteNameWithSpace, parentPath: split.parentPath };
|
||||
}
|
||||
}
|
||||
return splitName(recentLabel);
|
||||
|
|
|
@ -48,6 +48,25 @@ export function format2(template: string, values: Record<string, unknown>): stri
|
|||
return template.replace(_format2Regexp, (match, group) => (values[group] ?? match) as string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given value so that it can be used as literal value in html attributes.
|
||||
*
|
||||
* In other words, computes `$val`, such that `attr` in `<div attr="$val" />` has the runtime value `value`.
|
||||
* This prevents XSS injection.
|
||||
*/
|
||||
export function htmlAttributeEncodeValue(value: string): string {
|
||||
return value.replace(/[<>"'&]/g, ch => {
|
||||
switch (ch) {
|
||||
case '<': return '<';
|
||||
case '>': return '>';
|
||||
case '"': return '"';
|
||||
case '\'': return ''';
|
||||
case '&': return '&';
|
||||
}
|
||||
return ch;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HTML characters inside the string to use entities instead. Makes the string safe from
|
||||
* being used e.g. in HTMLElement.innerHTML.
|
||||
|
|
|
@ -532,3 +532,13 @@ suite('Strings', () => {
|
|||
|
||||
ensureNoDisposablesAreLeakedInTestSuite();
|
||||
});
|
||||
|
||||
test('htmlAttributeEncodeValue', () => {
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue(''), '');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('abc'), 'abc');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('<script>alert("Hello")</script>'), '<script>alert("Hello")</script>');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('Hello & World'), 'Hello & World');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('"Hello"'), '"Hello"');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('\'Hello\''), ''Hello'');
|
||||
assert.strictEqual(strings.htmlAttributeEncodeValue('<>&\'"'), '<>&'"');
|
||||
});
|
||||
|
|
|
@ -20,17 +20,17 @@ import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
|||
* This can end up producing multiple `LineDecorationToRender`.
|
||||
*/
|
||||
export class DecorationToRender {
|
||||
_decorationToRenderBrand: void = undefined;
|
||||
public readonly _decorationToRenderBrand: void = undefined;
|
||||
|
||||
public startLineNumber: number;
|
||||
public endLineNumber: number;
|
||||
public className: string;
|
||||
public readonly zIndex: number;
|
||||
|
||||
constructor(startLineNumber: number, endLineNumber: number, className: string, zIndex: number | undefined) {
|
||||
this.startLineNumber = +startLineNumber;
|
||||
this.endLineNumber = +endLineNumber;
|
||||
this.className = String(className);
|
||||
constructor(
|
||||
public readonly startLineNumber: number,
|
||||
public readonly endLineNumber: number,
|
||||
public readonly className: string,
|
||||
public readonly tooltip: string | null,
|
||||
zIndex: number | undefined,
|
||||
) {
|
||||
this.zIndex = zIndex ?? 0;
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ export class LineDecorationToRender {
|
|||
constructor(
|
||||
public readonly className: string,
|
||||
public readonly zIndex: number,
|
||||
public readonly tooltip: string | null,
|
||||
) { }
|
||||
}
|
||||
|
||||
|
@ -108,7 +109,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay {
|
|||
}
|
||||
|
||||
for (let i = startLineIndex; i <= prevEndLineIndex; i++) {
|
||||
output[i].add(new LineDecorationToRender(className, zIndex));
|
||||
output[i].add(new LineDecorationToRender(className, zIndex, d.tooltip));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,11 +78,11 @@ export class LinesDecorationsOverlay extends DedupOverlay {
|
|||
const linesDecorationsClassName = d.options.linesDecorationsClassName;
|
||||
const zIndex = d.options.zIndex;
|
||||
if (linesDecorationsClassName) {
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName, zIndex);
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName, d.options.linesDecorationsTooltip ?? null, zIndex);
|
||||
}
|
||||
const firstLineDecorationClassName = d.options.firstLineDecorationClassName;
|
||||
if (firstLineDecorationClassName) {
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.startLineNumber, firstLineDecorationClassName, zIndex);
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.startLineNumber, firstLineDecorationClassName, d.options.linesDecorationsTooltip ?? null, zIndex);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
|
@ -103,7 +103,12 @@ export class LinesDecorationsOverlay extends DedupOverlay {
|
|||
const decorations = toRender[lineIndex].getDecorations();
|
||||
let lineOutput = '';
|
||||
for (const decoration of decorations) {
|
||||
lineOutput += '<div class="cldr ' + decoration.className + common;
|
||||
let addition = '<div class="cldr ' + decoration.className;
|
||||
if (decoration.tooltip !== null) {
|
||||
addition += '" title="' + decoration.tooltip; // The tooltip is already escaped.
|
||||
}
|
||||
addition += common;
|
||||
lineOutput += addition;
|
||||
}
|
||||
output[lineIndex] = lineOutput;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ export class MarginViewLineDecorationsOverlay extends DedupOverlay {
|
|||
const marginClassName = d.options.marginClassName;
|
||||
const zIndex = d.options.zIndex;
|
||||
if (marginClassName) {
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, marginClassName, zIndex);
|
||||
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, marginClassName, null, zIndex);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
|
|
|
@ -1778,6 +1778,7 @@ export interface CommentReaction {
|
|||
readonly count?: number;
|
||||
readonly hasReacted?: boolean;
|
||||
readonly canEdit?: boolean;
|
||||
readonly reactors?: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -169,6 +169,10 @@ export interface IModelDecorationOptions {
|
|||
* If set, the decoration will be rendered in the lines decorations with this CSS class name.
|
||||
*/
|
||||
linesDecorationsClassName?: string | null;
|
||||
/**
|
||||
* Controls the tooltip text of the line decoration.
|
||||
*/
|
||||
linesDecorationsTooltip?: string | null;
|
||||
/**
|
||||
* If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping.
|
||||
*/
|
||||
|
|
|
@ -2297,6 +2297,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
readonly glyphMargin?: model.IModelDecorationGlyphMarginOptions | null | undefined;
|
||||
readonly glyphMarginClassName: string | null;
|
||||
readonly linesDecorationsClassName: string | null;
|
||||
readonly linesDecorationsTooltip: string | null;
|
||||
readonly firstLineDecorationClassName: string | null;
|
||||
readonly marginClassName: string | null;
|
||||
readonly inlineClassName: string | null;
|
||||
|
@ -2328,6 +2329,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
|||
this.glyphMargin = options.glyphMarginClassName ? new ModelDecorationGlyphMarginOptions(options.glyphMargin) : null;
|
||||
this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : null;
|
||||
this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : null;
|
||||
this.linesDecorationsTooltip = options.linesDecorationsTooltip ? strings.htmlAttributeEncodeValue(options.linesDecorationsTooltip) : null;
|
||||
this.firstLineDecorationClassName = options.firstLineDecorationClassName ? cleanClassName(options.firstLineDecorationClassName) : null;
|
||||
this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : null;
|
||||
this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : null;
|
||||
|
|
|
@ -24,6 +24,9 @@ export const foldingManualExpandedIcon = registerIcon('folding-manual-expanded',
|
|||
|
||||
const foldedBackgroundMinimap = { color: themeColorFromId(foldBackground), position: MinimapPosition.Inline };
|
||||
|
||||
const collapsed = localize('linesCollapsed', "Click to expand the range.");
|
||||
const expanded = localize('linesExpanded', "Click to collapse the range.");
|
||||
|
||||
export class FoldingDecorationProvider implements IDecorationProvider {
|
||||
|
||||
private static readonly COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
|
@ -31,6 +34,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon),
|
||||
});
|
||||
|
||||
|
@ -41,6 +45,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
className: 'folded-background',
|
||||
minimap: foldedBackgroundMinimap,
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingCollapsedIcon)
|
||||
});
|
||||
|
||||
|
@ -49,6 +54,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualCollapsedIcon)
|
||||
});
|
||||
|
||||
|
@ -59,6 +65,7 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
className: 'folded-background',
|
||||
minimap: foldedBackgroundMinimap,
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualCollapsedIcon)
|
||||
});
|
||||
|
||||
|
@ -66,7 +73,8 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
description: 'folding-no-controls-range-decoration',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
afterContentClassName: 'inline-folded',
|
||||
isWholeLine: true
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
});
|
||||
|
||||
private static readonly NO_CONTROLS_COLLAPSED_HIGHLIGHTED_RANGE_DECORATION = ModelDecorationOptions.register({
|
||||
|
@ -75,35 +83,40 @@ export class FoldingDecorationProvider implements IDecorationProvider {
|
|||
afterContentClassName: 'inline-folded',
|
||||
className: 'folded-background',
|
||||
minimap: foldedBackgroundMinimap,
|
||||
isWholeLine: true
|
||||
isWholeLine: true,
|
||||
linesDecorationsTooltip: collapsed,
|
||||
});
|
||||
|
||||
private static readonly EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-expanded-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingExpandedIcon),
|
||||
linesDecorationsTooltip: expanded,
|
||||
});
|
||||
|
||||
private static readonly EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-expanded-auto-hide-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingExpandedIcon)
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingExpandedIcon),
|
||||
linesDecorationsTooltip: expanded,
|
||||
});
|
||||
|
||||
private static readonly MANUALLY_EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-expanded-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingManualExpandedIcon)
|
||||
firstLineDecorationClassName: 'alwaysShowFoldIcons ' + ThemeIcon.asClassName(foldingManualExpandedIcon),
|
||||
linesDecorationsTooltip: expanded,
|
||||
});
|
||||
|
||||
private static readonly MANUALLY_EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({
|
||||
description: 'folding-manually-expanded-auto-hide-visual-decoration',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
|
||||
isWholeLine: true,
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualExpandedIcon)
|
||||
firstLineDecorationClassName: ThemeIcon.asClassName(foldingManualExpandedIcon),
|
||||
linesDecorationsTooltip: expanded,
|
||||
});
|
||||
|
||||
private static readonly NO_CONTROLS_EXPANDED_RANGE_DECORATION = ModelDecorationOptions.register({
|
||||
|
|
4
src/vs/monaco.d.ts
vendored
4
src/vs/monaco.d.ts
vendored
|
@ -1699,6 +1699,10 @@ declare namespace monaco.editor {
|
|||
* If set, the decoration will be rendered in the lines decorations with this CSS class name.
|
||||
*/
|
||||
linesDecorationsClassName?: string | null;
|
||||
/**
|
||||
* Controls the tooltip text of the line decoration.
|
||||
*/
|
||||
linesDecorationsTooltip?: string | null;
|
||||
/**
|
||||
* If set, the decoration will be rendered in the lines decorations with this CSS class name, but only for the first line in case of line wrapping.
|
||||
*/
|
||||
|
|
|
@ -137,6 +137,9 @@ async function doResolveUnixShellEnv(logService: ILogService, token: Cancellatio
|
|||
} else if (name === 'nu') { // nushell requires ^ before quoted path to treat it as a command
|
||||
command = `^'${process.execPath}' ${extraArgs} -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
|
||||
shellArgs = ['-i', '-l', '-c'];
|
||||
} else if (name === 'xonsh') { // #200374: native implementation is shorter
|
||||
command = `import os, json; print("${mark}", json.dumps(dict(os.environ)), "${mark}")`;
|
||||
shellArgs = ['-i', '-l', '-c'];
|
||||
} else {
|
||||
command = `'${process.execPath}' ${extraArgs} -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`;
|
||||
|
||||
|
|
|
@ -48,10 +48,37 @@ export class ThemeMainService extends Disposable implements IThemeMainService {
|
|||
constructor(@IStateService private stateService: IStateService, @IConfigurationService private configurationService: IConfigurationService) {
|
||||
super();
|
||||
|
||||
// System Theme
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('window.systemColorTheme')) {
|
||||
this.updateSystemColorTheme();
|
||||
}
|
||||
}));
|
||||
this.updateSystemColorTheme();
|
||||
|
||||
// Color Scheme changes
|
||||
nativeTheme.on('updated', () => {
|
||||
this._onDidChangeColorScheme.fire(this.getColorScheme());
|
||||
});
|
||||
this._register(Event.fromNodeEventEmitter(nativeTheme, 'updated')(() => this._onDidChangeColorScheme.fire(this.getColorScheme())));
|
||||
}
|
||||
|
||||
private updateSystemColorTheme(): void {
|
||||
switch (this.configurationService.getValue<'default' | 'auto' | 'light' | 'dark'>('window.systemColorTheme')) {
|
||||
case 'dark':
|
||||
nativeTheme.themeSource = 'dark';
|
||||
break;
|
||||
case 'light':
|
||||
nativeTheme.themeSource = 'light';
|
||||
break;
|
||||
case 'auto':
|
||||
switch (this.getBaseTheme()) {
|
||||
case 'vs': nativeTheme.themeSource = 'light'; break;
|
||||
case 'vs-dark': nativeTheme.themeSource = 'dark'; break;
|
||||
default: nativeTheme.themeSource = 'system';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
nativeTheme.themeSource = 'system';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getColorScheme(): IColorScheme {
|
||||
|
@ -86,8 +113,7 @@ export class ThemeMainService extends Disposable implements IThemeMainService {
|
|||
|
||||
let background = this.stateService.getItem<string | null>(THEME_BG_STORAGE_KEY, null);
|
||||
if (!background) {
|
||||
const baseTheme = this.stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
switch (baseTheme) {
|
||||
switch (this.getBaseTheme()) {
|
||||
case 'vs': background = DEFAULT_BG_LIGHT; break;
|
||||
case 'hc-black': background = DEFAULT_BG_HC_BLACK; break;
|
||||
case 'hc-light': background = DEFAULT_BG_HC_LIGHT; break;
|
||||
|
@ -102,6 +128,16 @@ export class ThemeMainService extends Disposable implements IThemeMainService {
|
|||
return background;
|
||||
}
|
||||
|
||||
private getBaseTheme(): 'vs' | 'vs-dark' | 'hc-black' | 'hc-light' {
|
||||
const baseTheme = this.stateService.getItem<string>(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0];
|
||||
switch (baseTheme) {
|
||||
case 'vs': return 'vs';
|
||||
case 'hc-black': return 'hc-black';
|
||||
case 'hc-light': return 'hc-light';
|
||||
default: return 'vs-dark';
|
||||
}
|
||||
}
|
||||
|
||||
saveWindowSplash(windowId: number | undefined, splash: IPartsSplash): void {
|
||||
|
||||
// Update in storage
|
||||
|
@ -115,6 +151,9 @@ export class ThemeMainService extends Disposable implements IThemeMainService {
|
|||
if (typeof windowId === 'number') {
|
||||
this.updateBackgroundColor(windowId, splash);
|
||||
}
|
||||
|
||||
// Update system theme
|
||||
this.updateSystemColorTheme();
|
||||
}
|
||||
|
||||
private updateBackgroundColor(windowId: number, splash: IPartsSplash): void {
|
||||
|
|
|
@ -673,6 +673,10 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
|||
checkProposedApiEnabled(extension, 'commentsDraftState');
|
||||
}
|
||||
|
||||
if (vscodeComment.reactions?.some(reaction => reaction.reactors !== undefined)) {
|
||||
checkProposedApiEnabled(extension, 'commentReactor');
|
||||
}
|
||||
|
||||
return {
|
||||
mode: vscodeComment.mode,
|
||||
contextValue: vscodeComment.contextValue,
|
||||
|
@ -693,6 +697,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
|||
iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined,
|
||||
count: reaction.count,
|
||||
hasReacted: reaction.authorHasReacted,
|
||||
reactors: reaction.reactors
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -701,7 +706,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
|
|||
label: reaction.label || '',
|
||||
count: reaction.count || 0,
|
||||
iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '',
|
||||
authorHasReacted: reaction.hasReacted || false
|
||||
authorHasReacted: reaction.hasReacted || false,
|
||||
reactors: reaction.reactors
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -459,7 +459,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
|
|||
}
|
||||
this.notificationService.error(error);
|
||||
}
|
||||
}, reaction.iconPath, reaction.count);
|
||||
}, reaction.reactors, reaction.iconPath, reaction.count);
|
||||
|
||||
this._reactionsActionBar?.push(action, { label: true, icon: true });
|
||||
});
|
||||
|
|
|
@ -20,6 +20,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
|
|||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CommentsModel, ICommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel';
|
||||
|
||||
export const ICommentService = createDecorator<ICommentService>('commentService');
|
||||
|
||||
|
@ -86,6 +87,7 @@ export interface ICommentService {
|
|||
readonly onDidDeleteDataProvider: Event<string | undefined>;
|
||||
readonly onDidChangeCommentingEnabled: Event<boolean>;
|
||||
readonly isCommentingEnabled: boolean;
|
||||
readonly commentsModel: ICommentsModel;
|
||||
setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void;
|
||||
setWorkspaceComments(owner: string, commentsByResource: CommentThread<IRange | ICellRange>[]): void;
|
||||
removeWorkspaceComments(owner: string): void;
|
||||
|
@ -162,6 +164,9 @@ export class CommentService extends Disposable implements ICommentService {
|
|||
private _continueOnComments = new Map<string, PendingCommentThread[]>(); // owner -> PendingCommentThread[]
|
||||
private _continueOnCommentProviders = new Set<IContinueOnCommentProvider>();
|
||||
|
||||
private readonly _commentsModel: CommentsModel = this._register(new CommentsModel());
|
||||
public readonly commentsModel: ICommentsModel = this._commentsModel;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService protected readonly instantiationService: IInstantiationService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
|
@ -200,7 +205,7 @@ export class CommentService extends Disposable implements ICommentService {
|
|||
removed: [],
|
||||
changed: []
|
||||
};
|
||||
this._onDidUpdateCommentThreads.fire(evt);
|
||||
this.updateModelThreads(evt);
|
||||
}
|
||||
}));
|
||||
this._register(storageService.onWillSaveState(() => {
|
||||
|
@ -272,20 +277,31 @@ export class CommentService extends Disposable implements ICommentService {
|
|||
this._onDidSetResourceCommentInfos.fire({ resource, commentInfos });
|
||||
}
|
||||
|
||||
private setModelThreads(ownerId: string, ownerLabel: string, commentThreads: CommentThread<IRange>[]) {
|
||||
this._commentsModel.setCommentThreads(ownerId, ownerLabel, commentThreads);
|
||||
this._onDidSetAllCommentThreads.fire({ ownerId, ownerLabel, commentThreads });
|
||||
}
|
||||
|
||||
private updateModelThreads(event: ICommentThreadChangedEvent) {
|
||||
this._commentsModel.updateCommentThreads(event);
|
||||
this._onDidUpdateCommentThreads.fire(event);
|
||||
}
|
||||
|
||||
setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void {
|
||||
|
||||
if (commentsByResource.length) {
|
||||
this._workspaceHasCommenting.set(true);
|
||||
}
|
||||
const control = this._commentControls.get(owner);
|
||||
if (control) {
|
||||
this._onDidSetAllCommentThreads.fire({ ownerId: owner, ownerLabel: control.label, commentThreads: commentsByResource });
|
||||
this.setModelThreads(owner, control.label, commentsByResource);
|
||||
}
|
||||
}
|
||||
|
||||
removeWorkspaceComments(owner: string): void {
|
||||
const control = this._commentControls.get(owner);
|
||||
if (control) {
|
||||
this._onDidSetAllCommentThreads.fire({ ownerId: owner, ownerLabel: control.label, commentThreads: [] });
|
||||
this.setModelThreads(owner, control.label, []);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,6 +316,7 @@ export class CommentService extends Disposable implements ICommentService {
|
|||
} else {
|
||||
this._commentControls.clear();
|
||||
}
|
||||
this._commentsModel.deleteCommentsByOwner(owner);
|
||||
this._onDidDeleteDataProvider.fire(owner);
|
||||
}
|
||||
|
||||
|
@ -346,7 +363,7 @@ export class CommentService extends Disposable implements ICommentService {
|
|||
const control = this._commentControls.get(ownerId);
|
||||
if (control) {
|
||||
const evt: ICommentThreadChangedEvent = Object.assign({}, event, { owner: ownerId, ownerLabel: control.label });
|
||||
this._onDidUpdateCommentThreads.fire(evt);
|
||||
this.updateModelThreads(evt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
|||
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import 'vs/workbench/contrib/comments/browser/commentsEditorContribution';
|
||||
import { ICommentService, CommentService } from 'vs/workbench/contrib/comments/browser/commentService';
|
||||
import { ICommentService, CommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { ctxCommentEditorFocused } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
@ -16,12 +16,17 @@ import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions,
|
|||
import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
|
||||
import { CommentCommandId } from 'vs/workbench/contrib/comments/common/commentCommandIds';
|
||||
import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/browser/toggleTabFocusMode';
|
||||
import { getActiveElement } from 'vs/base/browser/dom';
|
||||
import { Extensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { COMMENTS_VIEW_ID } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
|
||||
import { CommentThreadState } from 'vs/editor/common/languages';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
|
||||
id: 'comments',
|
||||
|
@ -68,7 +73,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
|||
|
||||
registerSingleton(ICommentService, CommentService, InstantiationType.Delayed);
|
||||
|
||||
|
||||
export namespace CommentAccessibilityHelpNLS {
|
||||
export const intro = nls.localize('intro', "The editor contains commentable range(s). Some useful commands include:");
|
||||
export const introWidget = nls.localize('introWidget', "This widget contains a text area, for composition of new comments, and actions, that can be tabbed to once tab moves focus mode has been enabled ({0}).");
|
||||
|
@ -134,3 +138,51 @@ export class CommentsAccessibilityHelpProvider implements IAccessibleContentProv
|
|||
this._element?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
export class UnresolvedCommentsBadge extends Disposable implements IWorkbenchContribution {
|
||||
private readonly activity = this._register(new MutableDisposable<IDisposable>());
|
||||
private totalUnresolved = 0;
|
||||
|
||||
constructor(
|
||||
@ICommentService private readonly _commentService: ICommentService,
|
||||
@IActivityService private readonly activityService: IActivityService) {
|
||||
super();
|
||||
this._register(this._commentService.onDidSetAllCommentThreads(this.onAllCommentsChanged, this));
|
||||
this._register(this._commentService.onDidUpdateCommentThreads(this.onCommentsUpdated, this));
|
||||
|
||||
}
|
||||
|
||||
private onAllCommentsChanged(e: IWorkspaceCommentThreadsEvent): void {
|
||||
let unresolved = 0;
|
||||
for (const thread of e.commentThreads) {
|
||||
if (thread.state === CommentThreadState.Unresolved) {
|
||||
unresolved++;
|
||||
}
|
||||
}
|
||||
this.updateBadge(unresolved);
|
||||
}
|
||||
|
||||
private onCommentsUpdated(): void {
|
||||
let unresolved = 0;
|
||||
for (const resource of this._commentService.commentsModel.resourceCommentThreads) {
|
||||
for (const thread of resource.commentThreads) {
|
||||
if (thread.threadState === CommentThreadState.Unresolved) {
|
||||
unresolved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.updateBadge(unresolved);
|
||||
}
|
||||
|
||||
private updateBadge(unresolved: number) {
|
||||
if (unresolved === this.totalUnresolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.totalUnresolved = unresolved;
|
||||
const message = nls.localize('totalUnresolvedComments', '{0} Unresolved Comments', this.totalUnresolved);
|
||||
this.activity.value = this.activityService.showViewActivity(COMMENTS_VIEW_ID, { badge: new NumberBadge(this.totalUnresolved, () => message) });
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(UnresolvedCommentsBadge, LifecyclePhase.Eventually);
|
||||
|
|
155
src/vs/workbench/contrib/comments/browser/commentsModel.ts
Normal file
155
src/vs/workbench/contrib/comments/browser/commentsModel.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { groupBy } from 'vs/base/common/arrays';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CommentThread } from 'vs/editor/common/languages';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface ICommentsModel {
|
||||
hasCommentThreads(): boolean;
|
||||
getMessage(): string;
|
||||
readonly resourceCommentThreads: ResourceWithCommentThreads[];
|
||||
readonly commentThreadsMap: Map<string, { resourceWithCommentThreads: ResourceWithCommentThreads[]; ownerLabel?: string }>;
|
||||
}
|
||||
|
||||
export class CommentsModel extends Disposable implements ICommentsModel {
|
||||
readonly _serviceBrand: undefined;
|
||||
private _resourceCommentThreads: ResourceWithCommentThreads[];
|
||||
get resourceCommentThreads(): ResourceWithCommentThreads[] { return this._resourceCommentThreads; }
|
||||
readonly commentThreadsMap: Map<string, { resourceWithCommentThreads: ResourceWithCommentThreads[]; ownerLabel?: string }>;
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super();
|
||||
this._resourceCommentThreads = [];
|
||||
this.commentThreadsMap = new Map<string, { resourceWithCommentThreads: ResourceWithCommentThreads[]; ownerLabel: string }>();
|
||||
}
|
||||
|
||||
private updateResourceCommentThreads() {
|
||||
const includeLabel = this.commentThreadsMap.size > 1;
|
||||
this._resourceCommentThreads = [...this.commentThreadsMap.values()].map(value => {
|
||||
return value.resourceWithCommentThreads.map(resource => {
|
||||
resource.ownerLabel = includeLabel ? value.ownerLabel : undefined;
|
||||
return resource;
|
||||
}).flat();
|
||||
}).flat();
|
||||
this._resourceCommentThreads.sort((a, b) => {
|
||||
return a.resource.toString() > b.resource.toString() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public setCommentThreads(owner: string, ownerLabel: string, commentThreads: CommentThread[]): void {
|
||||
this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: this.groupByResource(owner, commentThreads) });
|
||||
this.updateResourceCommentThreads();
|
||||
}
|
||||
|
||||
public deleteCommentsByOwner(owner?: string): void {
|
||||
if (owner) {
|
||||
const existingOwner = this.commentThreadsMap.get(owner);
|
||||
this.commentThreadsMap.set(owner, { ownerLabel: existingOwner?.ownerLabel, resourceWithCommentThreads: [] });
|
||||
} else {
|
||||
this.commentThreadsMap.clear();
|
||||
}
|
||||
this.updateResourceCommentThreads();
|
||||
}
|
||||
|
||||
public updateCommentThreads(event: ICommentThreadChangedEvent): boolean {
|
||||
const { owner, ownerLabel, removed, changed, added } = event;
|
||||
|
||||
const threadsForOwner = this.commentThreadsMap.get(owner)?.resourceWithCommentThreads || [];
|
||||
|
||||
removed.forEach(thread => {
|
||||
// Find resource that has the comment thread
|
||||
const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource);
|
||||
const matchingResourceData = matchingResourceIndex >= 0 ? threadsForOwner[matchingResourceIndex] : undefined;
|
||||
|
||||
// Find comment node on resource that is that thread and remove it
|
||||
const index = matchingResourceData?.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId) ?? 0;
|
||||
if (index >= 0) {
|
||||
matchingResourceData?.commentThreads.splice(index, 1);
|
||||
}
|
||||
|
||||
// If the comment thread was the last thread for a resource, remove that resource from the list
|
||||
if (matchingResourceData?.commentThreads.length === 0) {
|
||||
threadsForOwner.splice(matchingResourceIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
changed.forEach(thread => {
|
||||
// Find resource that has the comment thread
|
||||
const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource);
|
||||
const matchingResourceData = matchingResourceIndex >= 0 ? threadsForOwner[matchingResourceIndex] : undefined;
|
||||
if (!matchingResourceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find comment node on resource that is that thread and replace it
|
||||
const index = matchingResourceData.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId);
|
||||
if (index >= 0) {
|
||||
matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread);
|
||||
} else if (thread.comments && thread.comments.length) {
|
||||
matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread));
|
||||
}
|
||||
});
|
||||
|
||||
added.forEach(thread => {
|
||||
const existingResource = threadsForOwner.filter(resourceWithThreads => resourceWithThreads.resource.toString() === thread.resource);
|
||||
if (existingResource.length) {
|
||||
const resource = existingResource[0];
|
||||
if (thread.comments && thread.comments.length) {
|
||||
resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, resource.resource, thread));
|
||||
}
|
||||
} else {
|
||||
threadsForOwner.push(new ResourceWithCommentThreads(owner, URI.parse(thread.resource!), [thread]));
|
||||
}
|
||||
});
|
||||
|
||||
this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: threadsForOwner });
|
||||
this.updateResourceCommentThreads();
|
||||
|
||||
return removed.length > 0 || changed.length > 0 || added.length > 0;
|
||||
}
|
||||
|
||||
public hasCommentThreads(): boolean {
|
||||
return !!this._resourceCommentThreads.length;
|
||||
}
|
||||
|
||||
public getMessage(): string {
|
||||
if (!this._resourceCommentThreads.length) {
|
||||
return localize('noComments', "There are no comments in this workspace yet.");
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private groupByResource(owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] {
|
||||
const resourceCommentThreads: ResourceWithCommentThreads[] = [];
|
||||
const commentThreadsByResource = new Map<string, ResourceWithCommentThreads>();
|
||||
for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) {
|
||||
commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(owner, URI.parse(group[0].resource!), group));
|
||||
}
|
||||
|
||||
commentThreadsByResource.forEach((v, i, m) => {
|
||||
resourceCommentThreads.push(v);
|
||||
});
|
||||
|
||||
return resourceCommentThreads;
|
||||
}
|
||||
|
||||
private static _compareURIs(a: CommentThread, b: CommentThread) {
|
||||
const resourceA = a.resource!.toString();
|
||||
const resourceB = b.resource!.toString();
|
||||
if (resourceA < resourceB) {
|
||||
return -1;
|
||||
} else if (resourceA > resourceB) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
|||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
|
||||
import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel';
|
||||
import { CommentNode, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel';
|
||||
import { ITreeFilter, ITreeNode, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -31,6 +31,7 @@ import { openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser
|
|||
import { IStyleOverride } from 'vs/platform/theme/browser/defaultStyles';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { ILocalizedString } from 'vs/platform/action/common/action';
|
||||
import { CommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel';
|
||||
|
||||
export const COMMENTS_VIEW_ID = 'workbench.panel.comments';
|
||||
export const COMMENTS_VIEW_STORAGE_ID = 'Comments';
|
||||
|
|
|
@ -10,7 +10,7 @@ import { basename } from 'vs/base/common/resources';
|
|||
import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel';
|
||||
import { CommentNode, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel';
|
||||
import { IWorkspaceCommentThreadsEvent, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
|
||||
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
@ -35,20 +35,19 @@ import { CommentsFilters, CommentsFiltersChangeEvent } from 'vs/workbench/contri
|
|||
import { Memento, MementoObject } from 'vs/workbench/common/memento';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { FilterOptions } from 'vs/workbench/contrib/comments/browser/commentsFilterOptions';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { CommentThreadState } from 'vs/editor/common/languages';
|
||||
import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { CommentController } from 'vs/workbench/contrib/comments/browser/commentsController';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands';
|
||||
import { CommentsModel, ICommentsModel } from 'vs/workbench/contrib/comments/browser/commentsModel';
|
||||
|
||||
const CONTEXT_KEY_HAS_COMMENTS = new RawContextKey<boolean>('commentsView.hasComments', false);
|
||||
const CONTEXT_KEY_SOME_COMMENTS_EXPANDED = new RawContextKey<boolean>('commentsView.someCommentsExpanded', false);
|
||||
const VIEW_STORAGE_ID = 'commentsViewState';
|
||||
|
||||
function createResourceCommentsIterator(model: CommentsModel): Iterable<ITreeElement<ResourceWithCommentThreads | CommentNode>> {
|
||||
function createResourceCommentsIterator(model: ICommentsModel): Iterable<ITreeElement<ResourceWithCommentThreads | CommentNode>> {
|
||||
return Iterable.map(model.resourceCommentThreads, m => {
|
||||
const CommentNodeIt = Iterable.from(m.commentThreads);
|
||||
const children = Iterable.map(CommentNodeIt, r => ({ element: r }));
|
||||
|
@ -62,14 +61,11 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
private tree: CommentsList | undefined;
|
||||
private treeContainer!: HTMLElement;
|
||||
private messageBoxContainer!: HTMLElement;
|
||||
private commentsModel!: CommentsModel;
|
||||
private totalComments: number = 0;
|
||||
private totalUnresolved = 0;
|
||||
private readonly hasCommentsContextKey: IContextKey<boolean>;
|
||||
private readonly someCommentsExpandedContextKey: IContextKey<boolean>;
|
||||
private readonly filter: Filter;
|
||||
readonly filters: CommentsFilters;
|
||||
private readonly activity = this._register(new MutableDisposable<IDisposable>());
|
||||
|
||||
private currentHeight = 0;
|
||||
private currentWidth = 0;
|
||||
|
@ -93,7 +89,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
@ICommentService private readonly commentService: ICommentService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
|
||||
@IActivityService private readonly activityService: IActivityService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
const stateMemento = new Memento(VIEW_STORAGE_ID, storageService);
|
||||
|
@ -127,16 +122,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
this._register(this.filterWidget.onDidChangeFilterText(() => this.updateFilter()));
|
||||
}
|
||||
|
||||
private updateBadge(unresolved: number) {
|
||||
if (unresolved === this.totalUnresolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.totalUnresolved = unresolved;
|
||||
const message = nls.localize('totalUnresolvedComments', '{0} Unresolved Comments', this.totalUnresolved);
|
||||
this.activity.value = this.activityService.showViewActivity(this.id, { badge: new NumberBadge(this.totalUnresolved, () => message) });
|
||||
}
|
||||
|
||||
override saveState(): void {
|
||||
this.viewState['filter'] = this.filterWidget.getFilterText();
|
||||
this.viewState['filterHistory'] = this.filterWidget.getHistory();
|
||||
|
@ -201,7 +186,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
|
||||
this.treeContainer = dom.append(domContainer, dom.$('.tree-container'));
|
||||
this.treeContainer.classList.add('file-icon-themable-tree', 'show-file-icons');
|
||||
this.commentsModel = new CommentsModel();
|
||||
|
||||
this.cachedFilterStats = undefined;
|
||||
this.createTree();
|
||||
|
@ -232,7 +216,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.commentsModel.hasCommentThreads() && this.messageBoxContainer) {
|
||||
if (!this.commentService.commentsModel.hasCommentThreads() && this.messageBoxContainer) {
|
||||
this.messageBoxContainer.focus();
|
||||
} else if (this.tree) {
|
||||
this.tree.domFocus();
|
||||
|
@ -267,9 +251,9 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
}
|
||||
|
||||
private async renderComments(): Promise<void> {
|
||||
this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads());
|
||||
this.treeContainer.classList.toggle('hidden', !this.commentService.commentsModel.hasCommentThreads());
|
||||
this.renderMessage();
|
||||
await this.tree?.setChildren(null, createResourceCommentsIterator(this.commentsModel));
|
||||
await this.tree?.setChildren(null, createResourceCommentsIterator(this.commentService.commentsModel));
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
|
@ -311,8 +295,8 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
}
|
||||
|
||||
private renderMessage(): void {
|
||||
this.messageBoxContainer.textContent = this.commentsModel.getMessage();
|
||||
this.messageBoxContainer.classList.toggle('hidden', this.commentsModel.hasCommentThreads());
|
||||
this.messageBoxContainer.textContent = this.commentService.commentsModel.getMessage();
|
||||
this.messageBoxContainer.classList.toggle('hidden', this.commentService.commentsModel.hasCommentThreads());
|
||||
}
|
||||
|
||||
private createTree(): void {
|
||||
|
@ -437,15 +421,15 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
return;
|
||||
}
|
||||
if (this.isVisible()) {
|
||||
this.hasCommentsContextKey.set(this.commentsModel.hasCommentThreads());
|
||||
this.hasCommentsContextKey.set(this.commentService.commentsModel.hasCommentThreads());
|
||||
|
||||
this.treeContainer.classList.toggle('hidden', !this.commentsModel.hasCommentThreads());
|
||||
this.treeContainer.classList.toggle('hidden', !this.commentService.commentsModel.hasCommentThreads());
|
||||
this.cachedFilterStats = undefined;
|
||||
this.renderMessage();
|
||||
this.tree?.setChildren(null, createResourceCommentsIterator(this.commentsModel));
|
||||
this.tree?.setChildren(null, createResourceCommentsIterator(this.commentService.commentsModel));
|
||||
|
||||
if (this.tree.getSelection().length === 0 && this.commentsModel.hasCommentThreads()) {
|
||||
const firstComment = this.commentsModel.resourceCommentThreads[0].commentThreads[0];
|
||||
if (this.tree.getSelection().length === 0 && this.commentService.commentsModel.hasCommentThreads()) {
|
||||
const firstComment = this.commentService.commentsModel.resourceCommentThreads[0].commentThreads[0];
|
||||
if (firstComment) {
|
||||
this.tree.setFocus([firstComment]);
|
||||
this.tree.setSelection([firstComment]);
|
||||
|
@ -456,7 +440,6 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
|
||||
private onAllCommentsChanged(e: IWorkspaceCommentThreadsEvent): void {
|
||||
this.cachedFilterStats = undefined;
|
||||
this.commentsModel.setCommentThreads(e.ownerId, e.ownerLabel, e.commentThreads);
|
||||
this.totalComments += e.commentThreads.length;
|
||||
|
||||
let unresolved = 0;
|
||||
|
@ -465,37 +448,28 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView {
|
|||
unresolved++;
|
||||
}
|
||||
}
|
||||
this.updateBadge(unresolved);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private onCommentsUpdated(e: ICommentThreadChangedEvent): void {
|
||||
this.cachedFilterStats = undefined;
|
||||
const didUpdate = this.commentsModel.updateCommentThreads(e);
|
||||
|
||||
this.totalComments += e.added.length;
|
||||
this.totalComments -= e.removed.length;
|
||||
|
||||
let unresolved = 0;
|
||||
for (const resource of this.commentsModel.resourceCommentThreads) {
|
||||
for (const resource of this.commentService.commentsModel.resourceCommentThreads) {
|
||||
for (const thread of resource.commentThreads) {
|
||||
if (thread.threadState === CommentThreadState.Unresolved) {
|
||||
unresolved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.updateBadge(unresolved);
|
||||
|
||||
if (didUpdate) {
|
||||
this.refresh();
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private onDataProviderDeleted(owner: string | undefined): void {
|
||||
this.cachedFilterStats = undefined;
|
||||
this.commentsModel.deleteCommentsByOwner(owner);
|
||||
|
||||
this.totalComments = 0;
|
||||
this.refresh();
|
||||
}
|
||||
|
|
|
@ -66,27 +66,46 @@ export class ReactionActionViewItem extends ActionViewItem {
|
|||
'This is a tooltip for an emoji button so that the current user can toggle their reaction to a comment.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second is the name of the reaction.']
|
||||
}, "{0}{1} reaction", toggleMessage, action.label);
|
||||
} else if (action.count === 1) {
|
||||
return nls.localize({
|
||||
key: 'comment.reactionLabelOne', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is 1.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second is the name of the reaction.']
|
||||
}, "{0}1 reaction with {1}", toggleMessage, action.label);
|
||||
} else if (action.count > 1) {
|
||||
return nls.localize({
|
||||
key: 'comment.reactionLabelMany', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is greater than 1.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second is number of users who have reacted with that reaction, and the third is the name of the reaction.']
|
||||
}, "{0}{1} reactions with {2}", toggleMessage, action.count, action.label);
|
||||
} else if (action.reactors === undefined || action.reactors.length === 0) {
|
||||
if (action.count === 1) {
|
||||
return nls.localize({
|
||||
key: 'comment.reactionLabelOne', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is 1.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second is the name of the reaction.']
|
||||
}, "{0}1 reaction with {1}", toggleMessage, action.label);
|
||||
} else if (action.count > 1) {
|
||||
return nls.localize({
|
||||
key: 'comment.reactionLabelMany', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is greater than 1.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second is number of users who have reacted with that reaction, and the third is the name of the reaction.']
|
||||
}, "{0}{1} reactions with {2}", toggleMessage, action.count, action.label);
|
||||
}
|
||||
} else {
|
||||
if (action.reactors.length <= 10 && action.reactors.length === action.count) {
|
||||
return nls.localize({
|
||||
key: 'comment.reactionLessThanTen', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is less than or equal to 10.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second iis a list of the reactors, and the third is the name of the reaction.']
|
||||
}, "{0}{1} reacted with {2}", toggleMessage, action.reactors.join(', '), action.label);
|
||||
} else if (action.count > 1) {
|
||||
const displayedReactors = action.reactors.slice(0, 10);
|
||||
return nls.localize({
|
||||
key: 'comment.reactionMoreThanTen', comment: [
|
||||
'This is a tooltip for an emoji that is a "reaction" to a comment where the count of the reactions is less than or equal to 10.',
|
||||
'The emoji is also a button so that the current user can also toggle their own emoji reaction.',
|
||||
'The first arg is localized message "Toggle reaction" or empty if the user doesn\'t have permission to toggle the reaction, the second iis a list of the reactors, and the third is the name of the reaction.']
|
||||
}, "{0}{1} and {2} more reacted with {3}", toggleMessage, displayedReactors.join(', '), action.count - displayedReactors.length, action.label);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export class ReactionAction extends Action {
|
||||
static readonly ID = 'toolbar.toggle.reaction';
|
||||
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise<any>, public icon?: UriComponents, public count?: number) {
|
||||
constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise<any>, public readonly reactors?: readonly string[], public icon?: UriComponents, public count?: number) {
|
||||
super(ReactionAction.ID, label, cssClass, enabled, actionCallback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { Comment, CommentThread, CommentThreadChangedEvent, CommentThreadState } from 'vs/editor/common/languages';
|
||||
import { groupBy } from 'vs/base/common/arrays';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface ICommentThreadChangedEvent extends CommentThreadChangedEvent<IRange> {
|
||||
owner: string;
|
||||
|
@ -66,135 +64,3 @@ export class ResourceWithCommentThreads {
|
|||
}
|
||||
}
|
||||
|
||||
export class CommentsModel {
|
||||
resourceCommentThreads: ResourceWithCommentThreads[];
|
||||
commentThreadsMap: Map<string, { resourceWithCommentThreads: ResourceWithCommentThreads[]; ownerLabel?: string }>;
|
||||
|
||||
constructor() {
|
||||
this.resourceCommentThreads = [];
|
||||
this.commentThreadsMap = new Map<string, { resourceWithCommentThreads: ResourceWithCommentThreads[]; ownerLabel: string }>();
|
||||
}
|
||||
|
||||
private updateResourceCommentThreads() {
|
||||
const includeLabel = this.commentThreadsMap.size > 1;
|
||||
this.resourceCommentThreads = [...this.commentThreadsMap.values()].map(value => {
|
||||
return value.resourceWithCommentThreads.map(resource => {
|
||||
resource.ownerLabel = includeLabel ? value.ownerLabel : undefined;
|
||||
return resource;
|
||||
}).flat();
|
||||
}).flat();
|
||||
this.resourceCommentThreads.sort((a, b) => {
|
||||
return a.resource.toString() > b.resource.toString() ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
public setCommentThreads(owner: string, ownerLabel: string, commentThreads: CommentThread[]): void {
|
||||
this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: this.groupByResource(owner, commentThreads) });
|
||||
this.updateResourceCommentThreads();
|
||||
}
|
||||
|
||||
public deleteCommentsByOwner(owner?: string): void {
|
||||
if (owner) {
|
||||
const existingOwner = this.commentThreadsMap.get(owner);
|
||||
this.commentThreadsMap.set(owner, { ownerLabel: existingOwner?.ownerLabel, resourceWithCommentThreads: [] });
|
||||
} else {
|
||||
this.commentThreadsMap.clear();
|
||||
}
|
||||
this.updateResourceCommentThreads();
|
||||
}
|
||||
|
||||
public updateCommentThreads(event: ICommentThreadChangedEvent): boolean {
|
||||
const { owner, ownerLabel, removed, changed, added } = event;
|
||||
|
||||
const threadsForOwner = this.commentThreadsMap.get(owner)?.resourceWithCommentThreads || [];
|
||||
|
||||
removed.forEach(thread => {
|
||||
// Find resource that has the comment thread
|
||||
const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource);
|
||||
const matchingResourceData = matchingResourceIndex >= 0 ? threadsForOwner[matchingResourceIndex] : undefined;
|
||||
|
||||
// Find comment node on resource that is that thread and remove it
|
||||
const index = matchingResourceData?.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId) ?? 0;
|
||||
if (index >= 0) {
|
||||
matchingResourceData?.commentThreads.splice(index, 1);
|
||||
}
|
||||
|
||||
// If the comment thread was the last thread for a resource, remove that resource from the list
|
||||
if (matchingResourceData?.commentThreads.length === 0) {
|
||||
threadsForOwner.splice(matchingResourceIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
changed.forEach(thread => {
|
||||
// Find resource that has the comment thread
|
||||
const matchingResourceIndex = threadsForOwner.findIndex((resourceData) => resourceData.id === thread.resource);
|
||||
const matchingResourceData = matchingResourceIndex >= 0 ? threadsForOwner[matchingResourceIndex] : undefined;
|
||||
if (!matchingResourceData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find comment node on resource that is that thread and replace it
|
||||
const index = matchingResourceData.commentThreads.findIndex((commentThread) => commentThread.threadId === thread.threadId);
|
||||
if (index >= 0) {
|
||||
matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread);
|
||||
} else if (thread.comments && thread.comments.length) {
|
||||
matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, URI.parse(matchingResourceData.id), thread));
|
||||
}
|
||||
});
|
||||
|
||||
added.forEach(thread => {
|
||||
const existingResource = threadsForOwner.filter(resourceWithThreads => resourceWithThreads.resource.toString() === thread.resource);
|
||||
if (existingResource.length) {
|
||||
const resource = existingResource[0];
|
||||
if (thread.comments && thread.comments.length) {
|
||||
resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(owner, resource.resource, thread));
|
||||
}
|
||||
} else {
|
||||
threadsForOwner.push(new ResourceWithCommentThreads(owner, URI.parse(thread.resource!), [thread]));
|
||||
}
|
||||
});
|
||||
|
||||
this.commentThreadsMap.set(owner, { ownerLabel, resourceWithCommentThreads: threadsForOwner });
|
||||
this.updateResourceCommentThreads();
|
||||
|
||||
return removed.length > 0 || changed.length > 0 || added.length > 0;
|
||||
}
|
||||
|
||||
public hasCommentThreads(): boolean {
|
||||
return !!this.resourceCommentThreads.length;
|
||||
}
|
||||
|
||||
public getMessage(): string {
|
||||
if (!this.resourceCommentThreads.length) {
|
||||
return localize('noComments', "There are no comments in this workspace yet.");
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private groupByResource(owner: string, commentThreads: CommentThread[]): ResourceWithCommentThreads[] {
|
||||
const resourceCommentThreads: ResourceWithCommentThreads[] = [];
|
||||
const commentThreadsByResource = new Map<string, ResourceWithCommentThreads>();
|
||||
for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) {
|
||||
commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(owner, URI.parse(group[0].resource!), group));
|
||||
}
|
||||
|
||||
commentThreadsByResource.forEach((v, i, m) => {
|
||||
resourceCommentThreads.push(v);
|
||||
});
|
||||
|
||||
return resourceCommentThreads;
|
||||
}
|
||||
|
||||
private static _compareURIs(a: CommentThread, b: CommentThread) {
|
||||
const resourceA = a.resource!.toString();
|
||||
const resourceB = b.resource!.toString();
|
||||
if (resourceA < resourceB) {
|
||||
return -1;
|
||||
} else if (resourceA > resourceB) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ const breakpointHelperDecoration: IModelDecorationOptions = {
|
|||
description: 'breakpoint-helper-decoration',
|
||||
glyphMarginClassName: ThemeIcon.asClassName(icons.debugBreakpointHint),
|
||||
glyphMargin: { position: GlyphMarginLane.Right },
|
||||
glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointHelper', "Click to add a breakpoint.")),
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
};
|
||||
|
||||
|
|
|
@ -591,7 +591,7 @@ configurationRegistry.registerConfiguration({
|
|||
'*.jsx': '${capture}.js',
|
||||
'*.tsx': '${capture}.ts',
|
||||
'tsconfig.json': 'tsconfig.*.json',
|
||||
'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml',
|
||||
'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,7 +316,7 @@
|
|||
/* decoration styles */
|
||||
|
||||
.monaco-editor .inline-chat-inserted-range {
|
||||
background-color: var(--vscode-diffEditor-insertedTextBackground);
|
||||
background-color: var(--vscode-inlineChatDiff-inserted);
|
||||
}
|
||||
|
||||
.monaco-editor .inline-chat-inserted-range-linehighlight {
|
||||
|
|
|
@ -42,7 +42,7 @@ import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
|||
import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { EmptyResponse, ErrorResponse, ExpansionState, IInlineChatSessionService, ReplyResponse, Session, SessionExchange, SessionPrompt } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { EditModeStrategy, LivePreviewStrategy, LiveStrategy3, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
|
||||
import { EditModeStrategy, LivePreviewStrategy, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
|
||||
import { IInlineChatMessageAppender, InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
|
||||
import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatProgressItem, IInlineChatRequest, IInlineChatResponse, INLINE_CHAT_ID, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||
|
||||
|
@ -364,7 +364,7 @@ export class InlineChatController implements IEditorContribution {
|
|||
|
||||
switch (session.editMode) {
|
||||
case EditMode.Live:
|
||||
this._strategy = this._instaService.createInstance(LiveStrategy3, session, this._editor, this._zone.value);
|
||||
this._strategy = this._instaService.createInstance(LiveStrategy, session, this._editor, this._zone.value);
|
||||
break;
|
||||
case EditMode.Preview:
|
||||
this._strategy = this._instaService.createInstance(PreviewStrategy, session, this._zone.value);
|
||||
|
|
|
@ -156,6 +156,7 @@ export class Session {
|
|||
extension: provider.debugName,
|
||||
startTime: this._startTime.toISOString(),
|
||||
edits: false,
|
||||
finishedByEdit: false,
|
||||
rounds: '',
|
||||
undos: '',
|
||||
editMode
|
||||
|
|
|
@ -13,7 +13,7 @@ import { Codicon } from 'vs/base/common/codicons';
|
|||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ThemeIcon } from 'vs/base/common/themables';
|
||||
import { ThemeIcon, themeColorFromId } from 'vs/base/common/themables';
|
||||
import { ICodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
|
||||
|
@ -26,7 +26,7 @@ import { IDocumentDiff } from 'vs/editor/common/diff/documentDiffProvider';
|
|||
import { DetailedLineRangeMapping, LineRangeMapping } from 'vs/editor/common/diff/rangeMapping';
|
||||
import { IEditorDecorationsCollection } from 'vs/editor/common/editorCommon';
|
||||
import { TextEdit } from 'vs/editor/common/languages';
|
||||
import { ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||
import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel';
|
||||
|
@ -39,7 +39,7 @@ import { countWords, getNWords } from 'vs/workbench/contrib/chat/common/chatWord
|
|||
import { InlineChatFileCreatePreviewWidget, InlineChatLivePreviewWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget';
|
||||
import { ReplyResponse, Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||
import { InlineChatZoneWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
|
||||
import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||
import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||
|
||||
export abstract class EditModeStrategy {
|
||||
|
||||
|
@ -469,7 +469,28 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer
|
|||
|
||||
// ---
|
||||
|
||||
export class LiveStrategy3 extends EditModeStrategy {
|
||||
export class LiveStrategy extends EditModeStrategy {
|
||||
|
||||
private readonly _decoModifiedInteractedWith = ModelDecorationOptions.register({
|
||||
description: 'inline-chat-modified-interacted-with',
|
||||
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
|
||||
});
|
||||
|
||||
|
||||
private readonly _decoInsertedText = ModelDecorationOptions.register({
|
||||
description: 'inline-modified-line',
|
||||
className: 'inline-chat-inserted-range-linehighlight',
|
||||
isWholeLine: true,
|
||||
overviewRuler: {
|
||||
position: OverviewRulerLane.Full,
|
||||
color: themeColorFromId(overviewRulerInlineChatDiffInserted),
|
||||
}
|
||||
});
|
||||
|
||||
private readonly _decoInsertedTextRange = ModelDecorationOptions.register({
|
||||
description: 'inline-chat-inserted-range-linehighlight',
|
||||
className: 'inline-chat-inserted-range',
|
||||
});
|
||||
|
||||
private readonly _store: DisposableStore = new DisposableStore();
|
||||
private readonly _sessionStore: DisposableStore = new DisposableStore();
|
||||
|
@ -579,7 +600,7 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
}
|
||||
|
||||
const listener = this._session.textModelN.onDidChangeContent(async () => {
|
||||
await this._showDiff(false, false);
|
||||
await this._showDiff(false, false, false);
|
||||
});
|
||||
|
||||
try {
|
||||
|
@ -627,9 +648,8 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
}
|
||||
|
||||
|
||||
private readonly _decoModifiedInteractedWith = ModelDecorationOptions.register({ description: 'inline-chat-modified-interacted-with', stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges });
|
||||
|
||||
private async _showDiff(isFinalChanges: boolean, isAfterManualInteraction: boolean): Promise<Position | undefined> {
|
||||
private async _showDiff(isFinalChanges: boolean, isAfterManualInteraction: boolean, revealWidget: boolean): Promise<Position | undefined> {
|
||||
|
||||
const diff = await this._computeDiff();
|
||||
|
||||
|
@ -668,24 +688,17 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (innerChanges) {
|
||||
for (const { modifiedRange } of innerChanges) {
|
||||
newDecorations.push({
|
||||
range: modifiedRange,
|
||||
options: {
|
||||
description: 'inline-modified',
|
||||
className: 'inline-chat-inserted-range',
|
||||
}
|
||||
});
|
||||
newDecorations.push({
|
||||
range: modifiedRange,
|
||||
options: {
|
||||
description: 'inline-modified',
|
||||
className: 'inline-chat-inserted-range-linehighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
});
|
||||
}
|
||||
// highlight modified lines
|
||||
newDecorations.push({
|
||||
range: modifiedRange,
|
||||
options: this._decoInsertedText
|
||||
});
|
||||
|
||||
for (const innerChange of innerChanges ?? []) {
|
||||
newDecorations.push({
|
||||
range: innerChange.modifiedRange,
|
||||
options: this._decoInsertedTextRange
|
||||
});
|
||||
}
|
||||
|
||||
// original view zone
|
||||
|
@ -718,7 +731,7 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
class: ThemeIcon.asClassName(Codicon.check),
|
||||
run: () => {
|
||||
this._modifiedRangesThatHaveBeenInteractedWith.push(id);
|
||||
return this._showDiff(true, true);
|
||||
return this._showDiff(true, true, true);
|
||||
}
|
||||
}),
|
||||
toAction({
|
||||
|
@ -732,7 +745,7 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
edits.push(EditOperation.replace(innerChange.modifiedRange, originalValue));
|
||||
}
|
||||
this._session.textModelN.pushEditOperations(null, edits, () => null);
|
||||
return this._showDiff(true, true);
|
||||
return this._showDiff(true, true, true);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
@ -757,7 +770,7 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
: undefined;
|
||||
|
||||
this._sessionStore.add(this._session.textModelN.onDidChangeContent(e => {
|
||||
this._showDiff(true, true);
|
||||
this._showDiff(true, true, false);
|
||||
}));
|
||||
|
||||
const zoneLineNumber = this._zone.position!.lineNumber;
|
||||
|
@ -774,7 +787,9 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
if (widgetData) {
|
||||
this._zone.widget.setExtraButtons(widgetData.actions);
|
||||
this._zone.updatePositionAndHeight(widgetData.position);
|
||||
this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position);
|
||||
if (revealWidget) {
|
||||
this._editor.revealPositionInCenterIfOutsideViewport(widgetData.position);
|
||||
}
|
||||
|
||||
this._updateSummaryMessage(diff.changes, widgetData.index);
|
||||
|
||||
|
@ -807,7 +822,7 @@ export class LiveStrategy3 extends EditModeStrategy {
|
|||
this._previewZone.value.hide();
|
||||
}
|
||||
|
||||
return await this._showDiff(true, false);
|
||||
return await this._showDiff(true, false, true);
|
||||
}
|
||||
|
||||
protected _updateSummaryMessage(mappings: readonly LineRangeMapping[], index: number) {
|
||||
|
|
|
@ -174,7 +174,11 @@ export const inlineChatInputPlaceholderForeground = registerColor('inlineChatInp
|
|||
export const inlineChatInputBackground = registerColor('inlineChatInput.background', { dark: inputBackground, light: inputBackground, hcDark: inputBackground, hcLight: inputBackground }, localize('inlineChatInput.background', "Background color of the interactive editor input"));
|
||||
|
||||
export const inlineChatDiffInserted = registerColor('inlineChatDiff.inserted', { dark: transparent(diffInserted, .5), light: transparent(diffInserted, .5), hcDark: transparent(diffInserted, .5), hcLight: transparent(diffInserted, .5) }, localize('inlineChatDiff.inserted', "Background color of inserted text in the interactive editor input"));
|
||||
export const overviewRulerInlineChatDiffInserted = registerColor('editorOverviewRuler.inlineChatInserted', { dark: transparent(diffInserted, 0.6), light: transparent(diffInserted, 0.8), hcDark: transparent(diffInserted, 0.6), hcLight: transparent(diffInserted, 0.8) }, localize('editorOverviewRuler.inlineChatInserted', 'Overview ruler marker color for inline chat inserted content.'));
|
||||
|
||||
export const inlineChatDiffRemoved = registerColor('inlineChatDiff.removed', { dark: transparent(diffRemoved, .5), light: transparent(diffRemoved, .5), hcDark: transparent(diffRemoved, .5), hcLight: transparent(diffRemoved, .5) }, localize('inlineChatDiff.removed', "Background color of removed text in the interactive editor input"));
|
||||
export const overviewRulerInlineChatDiffRemoved = registerColor('editorOverviewRuler.inlineChatRemoved', { dark: transparent(diffRemoved, 0.6), light: transparent(diffRemoved, 0.8), hcDark: transparent(diffRemoved, 0.6), hcLight: transparent(diffRemoved, 0.8) }, localize('editorOverviewRuler.inlineChatRemoved', 'Overview ruler marker color for inline chat removed content.'));
|
||||
|
||||
|
||||
// settings
|
||||
|
||||
|
|
|
@ -1047,7 +1047,7 @@ const overviewRulerDeletedForeground = registerColor('editorOverviewRuler.delete
|
|||
|
||||
class DirtyDiffDecorator extends Disposable {
|
||||
|
||||
static createDecoration(className: string, options: { gutter: boolean; overview: { active: boolean; color: string }; minimap: { active: boolean; color: string }; isWholeLine: boolean }): ModelDecorationOptions {
|
||||
static createDecoration(className: string, tooltip: string | null, options: { gutter: boolean; overview: { active: boolean; color: string }; minimap: { active: boolean; color: string }; isWholeLine: boolean }): ModelDecorationOptions {
|
||||
const decorationOptions: IModelDecorationOptions = {
|
||||
description: 'dirty-diff-decoration',
|
||||
isWholeLine: options.isWholeLine,
|
||||
|
@ -1055,6 +1055,7 @@ class DirtyDiffDecorator extends Disposable {
|
|||
|
||||
if (options.gutter) {
|
||||
decorationOptions.linesDecorationsClassName = `dirty-diff-glyph ${className}`;
|
||||
decorationOptions.linesDecorationsTooltip = tooltip;
|
||||
}
|
||||
|
||||
if (options.overview.active) {
|
||||
|
@ -1096,31 +1097,33 @@ class DirtyDiffDecorator extends Disposable {
|
|||
const overview = decorations === 'all' || decorations === 'overview';
|
||||
const minimap = decorations === 'all' || decorations === 'minimap';
|
||||
|
||||
this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', {
|
||||
const diffAdded = nls.localize('diffAdded', 'Added lines');
|
||||
this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', diffAdded, {
|
||||
gutter,
|
||||
overview: { active: overview, color: overviewRulerAddedForeground },
|
||||
minimap: { active: minimap, color: minimapGutterAddedBackground },
|
||||
isWholeLine: true
|
||||
});
|
||||
this.addedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added-pattern', {
|
||||
this.addedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added-pattern', diffAdded, {
|
||||
gutter,
|
||||
overview: { active: overview, color: overviewRulerAddedForeground },
|
||||
minimap: { active: minimap, color: minimapGutterAddedBackground },
|
||||
isWholeLine: true
|
||||
});
|
||||
this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', {
|
||||
const diffModified = nls.localize('diffModified', 'Changed lines');
|
||||
this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', diffModified, {
|
||||
gutter,
|
||||
overview: { active: overview, color: overviewRulerModifiedForeground },
|
||||
minimap: { active: minimap, color: minimapGutterModifiedBackground },
|
||||
isWholeLine: true
|
||||
});
|
||||
this.modifiedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified-pattern', {
|
||||
this.modifiedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified-pattern', diffModified, {
|
||||
gutter,
|
||||
overview: { active: overview, color: overviewRulerModifiedForeground },
|
||||
minimap: { active: minimap, color: minimapGutterModifiedBackground },
|
||||
isWholeLine: true
|
||||
});
|
||||
this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', {
|
||||
this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', nls.localize('diffDeleted', 'Removed lines'), {
|
||||
gutter,
|
||||
overview: { active: overview, color: overviewRulerDeletedForeground },
|
||||
minimap: { active: minimap, color: minimapGutterDeletedBackground },
|
||||
|
|
|
@ -255,14 +255,14 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
|
|||
markdownDescription: localize('inputFontSize', "Controls the font size for the input message in pixels."),
|
||||
default: 13
|
||||
},
|
||||
'scm.inputMaxLines': {
|
||||
'scm.inputMaxLineCount': {
|
||||
type: 'number',
|
||||
markdownDescription: localize('inputMaxLines', "Controls the maximum number of lines that the input will auto-grow to."),
|
||||
minimum: 1,
|
||||
maximum: 50,
|
||||
default: 10
|
||||
},
|
||||
'scm.inputMinLines': {
|
||||
'scm.inputMinLineCount': {
|
||||
type: 'number',
|
||||
markdownDescription: localize('inputMinLines', "Controls the minimum number of lines that the input will auto-grow from."),
|
||||
minimum: 1,
|
||||
|
|
|
@ -93,7 +93,7 @@ import { fillEditorsDragData } from 'vs/workbench/browser/dnd';
|
|||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { CodeDataTransfers } from 'vs/platform/dnd/browser/dnd';
|
||||
import { FormatOnType } from 'vs/editor/contrib/format/browser/formatActions';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorOption, EditorOptions, IRulerOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
|
@ -2142,7 +2142,8 @@ class SCMInputWidget {
|
|||
overflowWidgetsDomNode,
|
||||
formatOnType: true,
|
||||
renderWhitespace: 'none',
|
||||
dropIntoEditor: { enabled: true }
|
||||
dropIntoEditor: { enabled: true },
|
||||
...this.getInputEditorLanguageConfiguration(),
|
||||
};
|
||||
|
||||
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
|
||||
|
@ -2208,7 +2209,9 @@ class SCMInputWidget {
|
|||
'editor.fontFamily', // When `scm.inputFontFamily` is 'editor', we use it as an effective value
|
||||
'scm.inputFontSize',
|
||||
'editor.accessibilitySupport',
|
||||
'editor.cursorBlinking'
|
||||
'editor.cursorBlinking',
|
||||
'editor.rulers',
|
||||
'editor.wordWrap'
|
||||
];
|
||||
|
||||
const onRelevantSettingChanged = Event.filter(
|
||||
|
@ -2235,7 +2238,8 @@ class SCMInputWidget {
|
|||
fontSize: fontSize,
|
||||
lineHeight: lineHeight,
|
||||
accessibilitySupport,
|
||||
cursorBlinking
|
||||
cursorBlinking,
|
||||
...this.getInputEditorLanguageConfiguration()
|
||||
});
|
||||
|
||||
this.setPlaceholderFontStyles(fontFamily, fontSize, lineHeight);
|
||||
|
@ -2267,11 +2271,11 @@ class SCMInputWidget {
|
|||
const lineHeight = this.computeLineHeight(fontSize);
|
||||
const { top, bottom } = this.inputEditor.getOption(EditorOption.padding);
|
||||
|
||||
const inputMinLinesConfig = this.configurationService.getValue('scm.inputMinLines');
|
||||
const inputMinLinesConfig = this.configurationService.getValue('scm.inputMinLineCount');
|
||||
const inputMinLines = typeof inputMinLinesConfig === 'number' ? clamp(inputMinLinesConfig, 1, 50) : 1;
|
||||
const editorMinHeight = inputMinLines * lineHeight + top + bottom;
|
||||
|
||||
const inputMaxLinesConfig = this.configurationService.getValue('scm.inputMaxLines');
|
||||
const inputMaxLinesConfig = this.configurationService.getValue('scm.inputMaxLineCount');
|
||||
const inputMaxLines = typeof inputMaxLinesConfig === 'number' ? clamp(inputMaxLinesConfig, 1, 50) : 10;
|
||||
const editorMaxHeight = inputMaxLines * lineHeight + top + bottom;
|
||||
|
||||
|
@ -2412,6 +2416,16 @@ class SCMInputWidget {
|
|||
return this.configurationService.getValue<number>('scm.inputFontSize');
|
||||
}
|
||||
|
||||
private getInputEditorLanguageConfiguration(): { rulers: (number | IRulerOption)[]; wordWrap: 'off' | 'on' | 'wordWrapColumn' | 'bounded' } {
|
||||
const rulers = this.configurationService.inspect('editor.rulers', { overrideIdentifier: 'scminput' });
|
||||
const wordWrap = this.configurationService.inspect('editor.wordWrap', { overrideIdentifier: 'scminput' });
|
||||
|
||||
return {
|
||||
rulers: rulers.overrideIdentifiers?.includes('scminput') ? EditorOptions.rulers.validate(rulers.value) : [],
|
||||
wordWrap: wordWrap.overrideIdentifiers?.includes('scminput') ? EditorOptions.wordWrap.validate(wordWrap) : 'on'
|
||||
};
|
||||
}
|
||||
|
||||
private getToolbarWidth(): number {
|
||||
const showInputActionButton = this.configurationService.getValue<boolean>('scm.showInputActionButton');
|
||||
if (!this.toolbar || !showInputActionButton || this.toolbar?.isEmpty() === true) {
|
||||
|
@ -2648,8 +2662,8 @@ export class SCMViewPane extends ViewPane {
|
|||
e.affectsConfiguration('scm.alwaysShowRepositories') ||
|
||||
e.affectsConfiguration('scm.showIncomingChanges') ||
|
||||
e.affectsConfiguration('scm.showOutgoingChanges') ||
|
||||
e.affectsConfiguration('scm.inputMinLines') ||
|
||||
e.affectsConfiguration('scm.inputMaxLines')) {
|
||||
e.affectsConfiguration('scm.inputMinLineCount') ||
|
||||
e.affectsConfiguration('scm.inputMaxLineCount')) {
|
||||
this._showActionButton = this.configurationService.getValue<boolean>('scm.showActionButton');
|
||||
this._alwaysShowRepositories = this.configurationService.getValue<boolean>('scm.alwaysShowRepositories');
|
||||
this._showIncomingChanges = this.configurationService.getValue<ShowChangesSetting>('scm.showIncomingChanges');
|
||||
|
@ -2854,6 +2868,12 @@ export class SCMViewPane extends ViewPane {
|
|||
return;
|
||||
}
|
||||
|
||||
// Do not set focus/selection when the resource is already focused and selected
|
||||
if (this.tree.getFocus().some(e => isSCMResource(e) && this.uriIdentityService.extUri.isEqual(e.sourceUri, uri)) &&
|
||||
this.tree.getSelection().some(e => isSCMResource(e) && this.uriIdentityService.extUri.isEqual(e.sourceUri, uri))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.revealResourceThrottler.queue(
|
||||
() => this.treeOperationSequencer.queue(
|
||||
async () => {
|
||||
|
|
|
@ -960,6 +960,15 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
|||
return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`));
|
||||
}
|
||||
|
||||
this._fireTaskEvent(TaskEvent.start(task, terminal.instanceId, resolver.values));
|
||||
const mapKey = task.getMapKey();
|
||||
this._busyTasks[mapKey] = task;
|
||||
this._fireTaskEvent(TaskEvent.general(TaskEventKind.Active, task, terminal.instanceId));
|
||||
|
||||
const problemMatchers = await this._resolveMatchers(resolver, task.configurationProperties.problemMatchers);
|
||||
const startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this._markerService, this._modelService, ProblemHandlingStrategy.Clean, this._fileService);
|
||||
this._terminalStatusManager.addTerminal(task, terminal, startStopProblemMatcher);
|
||||
|
||||
let processStartedSignaled = false;
|
||||
terminal.processReady.then(() => {
|
||||
if (!processStartedSignaled) {
|
||||
|
@ -969,13 +978,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
|
|||
}, (_error) => {
|
||||
// The process never got ready. Need to think how to handle this.
|
||||
});
|
||||
this._fireTaskEvent(TaskEvent.start(task, terminal.instanceId, resolver.values));
|
||||
const mapKey = task.getMapKey();
|
||||
this._busyTasks[mapKey] = task;
|
||||
this._fireTaskEvent(TaskEvent.general(TaskEventKind.Active, task, terminal.instanceId));
|
||||
const problemMatchers = await this._resolveMatchers(resolver, task.configurationProperties.problemMatchers);
|
||||
const startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this._markerService, this._modelService, ProblemHandlingStrategy.Clean, this._fileService);
|
||||
this._terminalStatusManager.addTerminal(task, terminal, startStopProblemMatcher);
|
||||
|
||||
const onData = terminal.onLineData((line) => {
|
||||
startStopProblemMatcher.processLine(line);
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ export const allApiProposals = Object.freeze({
|
|||
chatVariables: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatVariables.d.ts',
|
||||
codeActionAI: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionAI.d.ts',
|
||||
codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts',
|
||||
commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts',
|
||||
commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts',
|
||||
contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts',
|
||||
contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts',
|
||||
|
|
|
@ -208,7 +208,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
const initializeColorTheme = async () => {
|
||||
const devThemes = this.colorThemeRegistry.findThemeByExtensionLocation(extDevLoc);
|
||||
if (devThemes.length) {
|
||||
return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY);
|
||||
const matchedColorTheme = devThemes.find(theme => theme.type === this.currentColorTheme.type);
|
||||
return this.setColorTheme(matchedColorTheme ? matchedColorTheme.id : devThemes[0].id, undefined);
|
||||
}
|
||||
|
||||
const preferredColorScheme = this.getPreferredColorScheme();
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'properties': {
|
||||
'window.systemColorTheme': {
|
||||
'type': 'string',
|
||||
'enum': ['default', 'auto', 'light', 'dark'],
|
||||
'enumDescriptions': [
|
||||
localize('window.systemColorTheme.default', "System color theme matches the configured OS theme."),
|
||||
localize('window.systemColorTheme.auto', "Enforce a light system color theme when a light workbench color theme is configured and the same for configured dark workbench color themes."),
|
||||
localize('window.systemColorTheme.light', "Enforce a light system color theme."),
|
||||
localize('window.systemColorTheme.dark', "Enforce a dark system color theme."),
|
||||
],
|
||||
'markdownDescription': localize('window.systemColorTheme', "The system color theme applies to native UI elements such as native dialogs, menus and title bar. Even if your OS is configured in light appearance mode, you can select a dark system color theme for the window. You can also configure to automatically adjust based on the `#workbench.colorTheme#` setting."),
|
||||
'default': 'default',
|
||||
'included': !isLinux, // not supported on Linux (https://github.com/electron/electron/issues/28887)
|
||||
'scope': ConfigurationScope.APPLICATION
|
||||
}
|
||||
}
|
||||
});
|
|
@ -134,8 +134,9 @@ import 'vs/workbench/contrib/configExporter/electron-sandbox/configurationExport
|
|||
// Terminal
|
||||
import 'vs/workbench/contrib/terminal/electron-sandbox/terminal.contribution';
|
||||
|
||||
// Themes Support
|
||||
// Themes
|
||||
import 'vs/workbench/contrib/themes/browser/themes.test.contribution';
|
||||
import 'vs/workbench/services/themes/electron-sandbox/themes.contribution';
|
||||
|
||||
// User Data Sync
|
||||
import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution';
|
||||
|
|
8
src/vscode-dts/vscode.d.ts
vendored
8
src/vscode-dts/vscode.d.ts
vendored
|
@ -2012,10 +2012,10 @@ declare module 'vscode' {
|
|||
|
||||
/**
|
||||
* A set of file filters that are used by the dialog. Each entry is a human-readable label,
|
||||
* like "TypeScript", and an array of extensions, e.g.
|
||||
* like "TypeScript", and an array of extensions, for example:
|
||||
* ```ts
|
||||
* {
|
||||
* 'Images': ['png', 'jpg']
|
||||
* 'Images': ['png', 'jpg'],
|
||||
* 'TypeScript': ['ts', 'tsx']
|
||||
* }
|
||||
* ```
|
||||
|
@ -2047,10 +2047,10 @@ declare module 'vscode' {
|
|||
|
||||
/**
|
||||
* A set of file filters that are used by the dialog. Each entry is a human-readable label,
|
||||
* like "TypeScript", and an array of extensions, e.g.
|
||||
* like "TypeScript", and an array of extensions, for example:
|
||||
* ```ts
|
||||
* {
|
||||
* 'Images': ['png', 'jpg']
|
||||
* 'Images': ['png', 'jpg'],
|
||||
* 'TypeScript': ['ts', 'tsx']
|
||||
* }
|
||||
* ```
|
||||
|
|
13
src/vscode-dts/vscode.proposed.commentReactor.d.ts
vendored
Normal file
13
src/vscode-dts/vscode.proposed.commentReactor.d.ts
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
// @alexr00 https://github.com/microsoft/vscode/issues/201131
|
||||
|
||||
export interface CommentReaction {
|
||||
readonly reactors?: readonly string[];
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue