Merge from vscode 071a5cf16fc999727acc31c790d78f750fa4b166 (#5261)

This commit is contained in:
Anthony Dresser
2019-04-30 07:54:56 -07:00
committed by GitHub
parent 02916aeffa
commit aacc0eca67
24 changed files with 564 additions and 238 deletions

View File

@@ -1,7 +1,7 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';

View File

@@ -1,6 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*----------------------------------------------------------

View File

@@ -1211,6 +1211,16 @@ export interface Command {
arguments?: any[];
}
/**
* @internal
*/
export interface CommentThreadTemplate {
label: string;
acceptInputCommand?: Command;
additionalCommands?: Command[];
deleteCommand?: Command;
}
/**
* @internal
*/
@@ -1220,6 +1230,7 @@ export interface CommentInfo {
commentingRanges?: (IRange[] | CommentingRanges);
reply?: Command;
draftMode?: DraftMode;
template?: CommentThreadTemplate;
}
/**
@@ -1298,8 +1309,7 @@ export interface CommentThread2 {
export interface CommentingRanges {
readonly resource: URI;
ranges: IRange[];
newCommentThreadCommand?: Command;
newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise<void>;
newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise<CommentThread | undefined>;
}
/**

View File

@@ -19,7 +19,6 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ILogService } from 'vs/platform/log/common/log';
import { SnippetSession } from './snippetSession';
import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState';
export class SnippetController2 implements IEditorContribution {
@@ -114,26 +113,10 @@ export class SnippetController2 implements IEditorContribution {
this._updateState();
// we listen on model and selection changes. usually
// both events come in together and this is to prevent
// that we don't call _updateState twice.
let state: EditorState;
let dedupedUpdateState = () => {
if (!state || !state.validate(this._editor)) {
this._updateState();
state = new EditorState(this._editor, CodeEditorStateFlag.Selection | CodeEditorStateFlag.Value);
}
};
this._snippetListener = [
this._editor.onDidChangeModelContent(e => {
if (e.isFlush) {
this.cancel();
} else {
setTimeout(dedupedUpdateState, 0);
}
}),
this._editor.onDidChangeCursorSelection(dedupedUpdateState),
this._editor.onDidChangeModelContent(e => e.isFlush && this.cancel()),
this._editor.onDidChangeModel(() => this.cancel()),
this._editor.onDidChangeCursorSelection(() => this._updateState())
];
}

View File

@@ -198,6 +198,10 @@ export class OneSnippet {
let ranges: Range[] | undefined;
for (const placeholder of placeholdersWithEqualIndex) {
if (placeholder.isFinalTabstop) {
// ignore those
break;
}
if (!ranges) {
ranges = [];
@@ -571,12 +575,6 @@ export class SnippetSession {
return false;
}
if (allPossibleSelections.has(0)) {
// selection overlaps with a final tab stop which means
// we done
return false;
}
// add selections from 'this' snippet so that we know all
// selections for this placeholder
allPossibleSelections.forEach((array, index) => {

View File

@@ -11,7 +11,6 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { NullLogService } from 'vs/platform/log/common/log';
import { Handler } from 'vs/editor/common/editorCommon';
import { timeout } from 'vs/base/common/async';
suite('SnippetController2', function () {
@@ -134,11 +133,11 @@ suite('SnippetController2', function () {
assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11));
editor.trigger('test', 'cut', {});
assertContextKeys(contextKeys, false, false, false);
assertContextKeys(contextKeys, true, false, true);
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5));
editor.trigger('test', 'type', { text: 'abc' });
assertContextKeys(contextKeys, false, false, false);
assertContextKeys(contextKeys, true, false, true);
ctrl.next();
assertContextKeys(contextKeys, false, false, false);
@@ -160,9 +159,9 @@ suite('SnippetController2', function () {
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
assertContextKeys(contextKeys, true, false, true);
// ctrl.next();
// assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
// assertContextKeys(contextKeys, true, true, true);
ctrl.next();
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
assertContextKeys(contextKeys, true, true, true);
ctrl.next();
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
@@ -177,10 +176,10 @@ suite('SnippetController2', function () {
ctrl.insert('farboo');
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
// assertContextKeys(contextKeys, true, false, true);
assertContextKeys(contextKeys, true, false, true);
// ctrl.next();
// assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
ctrl.next();
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
assertContextKeys(contextKeys, false, false, false);
});
@@ -403,22 +402,12 @@ suite('SnippetController2', function () {
assertSelections(editor, new Selection(1, 22, 1, 22));
});
test('A little confusing visual effect of highlighting for snippet tabstop #43270', async function () {
test('User defined snippet tab stops ignored #72862', function () {
const ctrl = new SnippetController2(editor, logService, contextKeys);
model.setValue('');
editor.setSelection(new Selection(1, 1, 1, 1));
ctrl.insert('background-color: ${1:fff};$0');
assertSelections(editor, new Selection(1, 19, 1, 22));
editor.setSelection(new Selection(1, 22, 1, 22));
ctrl.insert('export default $1');
assertContextKeys(contextKeys, true, false, true);
editor.trigger('', 'deleteRight', null);
assert.equal(model.getValue(), 'background-color: fff');
await timeout(0); // this depends on re-scheduling of events...
assertContextKeys(contextKeys, false, false, false);
});
});

View File

@@ -331,7 +331,7 @@ suite('SnippetSession', function () {
// reset selection to placeholder
session.next();
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.equal(session.isSelectionWithinPlaceholders(), true);
assert.equal(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17));
});

View File

@@ -814,28 +814,35 @@ declare module 'vscode' {
/**
* A comment is displayed within the editor or the Comments Panel, depending on how it is provided.
*/
export interface Comment {
export class Comment {
/**
* The id of the comment
*/
readonly id: string;
/**
* The id of the comment
*
* @deprecated Use Id instead
*/
readonly commentId: string;
/**
* The text of the comment
* The human-readable comment body
*/
readonly body: MarkdownString;
/**
* The display name of the user who created the comment
*/
readonly userName: string;
/**
* Optional label describing the [Comment](#Comment)
* Label will be rendered next to userName if exists.
*/
readonly label?: string;
/**
* The display name of the user who created the comment
*/
readonly userName: string;
/**
* The icon path for the user who created the comment
*/
@@ -896,6 +903,13 @@ declare module 'vscode' {
* Proposed Comment Reaction
*/
commentReactions?: CommentReaction[];
/**
* @param id The id of the comment
* @param body The human-readable comment body
* @param userName The display name of the user who created the comment
*/
constructor(id: string, body: MarkdownString, userName: string);
}
/**
@@ -925,6 +939,7 @@ declare module 'vscode' {
/**
* Comment Reactions
* Stay in proposed.
*/
interface CommentReaction {
readonly label?: string;
@@ -1006,6 +1021,9 @@ declare module 'vscode' {
value: string;
}
/**
* Stay in proposed
*/
export interface CommentReactionProvider {
availableReactions: CommentReaction[];
toggleReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise<void>;
@@ -1016,12 +1034,50 @@ declare module 'vscode' {
* Provide a list of ranges which allow new comment threads creation or null for a given document
*/
provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult<Range[]>;
}
export interface CommentThreadTemplate {
/**
* The human-readable label describing the [Comment Thread](#CommentThread)
*/
label: string;
/**
* The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette.
* Extensions still need to call `createCommentThread` inside this call when appropriate.
* Optional accept input command
*
* `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost.
* This command will be invoked when users the user accepts the value in the comment editor.
* This command will disabled when the comment editor is empty.
*/
createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult<void>;
acceptInputCommand?: Command;
/**
* Optional additonal commands.
*
* `additionalCommands` are the secondary actions rendered on Comment Widget.
*/
additionalCommands?: Command[];
/**
* The command to be executed when users try to delete the comment thread. Currently, this is only called
* when the user collapses a comment thread that has no comments in it.
*/
deleteCommand?: Command;
}
export interface EmptyCommentThreadFactory {
template: CommentThreadTemplate;
/**
* When users attempt to create new comment thread from the gutter or command palette, `template` will be used first to create the Comment Thread Widget in the editor for users to start comment drafting.
* Then `createEmptyCommentThread` is called after that. Extensions should still call [`createCommentThread`](CommentController.createCommentThread) to create a real [`CommentThread`](#CommentThread)
* Extensions still need to call `createCommentThread` inside this call when appropriate.
*
* @param document The document in which users attempt to create a new comment thread
* @param range The range the comment threadill located within the document.
*
* @returns commentThread The [`CommentThread`](#CommentThread) created by extensions
*/
createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult<CommentThread>;
}
export interface CommentController {
@@ -1036,23 +1092,44 @@ declare module 'vscode' {
readonly label: string;
/**
* The active (focused) [comment input box](#CommentInputBox).
* The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of
* the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox.
*/
readonly inputBox?: CommentInputBox;
readonly inputBox: CommentInputBox | undefined;
/**
* Create a [CommentThread](#CommentThread)
* The active [comment thread](#CommentThread) or `undefined`. The `activeCommentThread` is the comment thread of
* the comment thread widget that currently has focus. It's `undefined` when the focus is not in any comment thread widget.
*/
readonly activeCommentThread: CommentThread | undefined;
/**
* Create a [CommentThread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches)
* and Comments Panel.
* @param id An `id` for the comment thread.
* @param resource The uri of the document the thread has been created on.
* @param range The range the comment thread is located within the document.
* @param comments The ordered comments of the thread.
*/
createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread;
/**
* Optional commenting range provider.
* Provide a list [ranges](#Range) which support commenting to any given resource uri.
* Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri.
*
* If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor.
*/
commentingRangeProvider?: CommentingRangeProvider;
/**
* Optional empty comment thread factory. It's necessary for supporting users to trigger Comment Thread creation from the editor or command palette.
*
* If not provided, users won't be able to trigger new comment thread creation from the editor gutter area or command palette.
*/
emptyCommentThreadFactory?: EmptyCommentThreadFactory;
/**
* Optional reaction provider
* Stay in proposed.
*/
reactionProvider?: CommentReactionProvider;

View File

@@ -298,12 +298,18 @@ export class MainThreadCommentController {
);
this._threads.set(commentThreadHandle, thread);
this._commentService.updateComments(this._uniqueId, {
added: [thread],
removed: [],
changed: [],
draftMode: modes.DraftMode.NotSupported
});
// As we create comment thread from template and then restore from the newly created maint thread comment thread,
// we postpone the update event to avoid duplication.
// This can be actually removed once we are on the new API.
setTimeout(() => {
this._commentService.updateComments(this._uniqueId, {
added: [thread],
removed: [],
changed: [],
draftMode: modes.DraftMode.NotSupported
});
}, 0);
return thread;
}
@@ -379,10 +385,17 @@ export class MainThreadCommentController {
commentingRanges: commentingRanges ?
{
resource: resource, ranges: commentingRanges, newCommentThreadCallback: async (uri: UriComponents, range: IRange) => {
await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token);
let threadHandle = await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token);
if (threadHandle !== undefined) {
return this.getKnownThread(threadHandle);
}
return undefined; // {{SQL CARBON EDIT}} @anthonydresser revert back after strict-null-check
}
} : [],
draftMode: modes.DraftMode.NotSupported
draftMode: modes.DraftMode.NotSupported,
template: this._features.commentThreadTemplate
};
}
@@ -458,6 +471,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined);
}));
await this._proxy.$onActiveCommentThreadChange(controller.handle, controller.activeCommentThread.commentThreadHandle);
await this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined);
}));
}
@@ -510,8 +524,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments
return undefined;
}
console.log('createCommentThread', commentThreadHandle);
return provider.createCommentThread(commentThreadHandle, threadId, resource, range);
}

View File

@@ -121,11 +121,19 @@ export interface MainThreadCommandsShape extends IDisposable {
$getCommands(): Promise<string[]>;
}
export interface CommentThreadTemplate {
label: string;
acceptInputCommand?: modes.Command;
additionalCommands?: modes.Command[];
deleteCommand?: modes.Command;
}
export interface CommentProviderFeatures {
startDraftLabel?: string;
deleteDraftLabel?: string;
finishDraftLabel?: string;
reactionGroup?: modes.CommentReaction[];
commentThreadTemplate?: CommentThreadTemplate;
}
export interface MainThreadCommentsShape extends IDisposable {
@@ -1206,10 +1214,11 @@ export interface ExtHostCommentsShape {
$provideDocumentComments(handle: number, document: UriComponents): Promise<modes.CommentInfo | null>;
$createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise<modes.CommentThread | null>;
$onCommentWidgetInputChange(commentControllerHandle: number, input: string | undefined): Promise<number | undefined>;
$onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number | undefined): Promise<number | undefined>;
$provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<IRange[] | undefined>;
$provideReactionGroup(commentControllerHandle: number): Promise<modes.CommentReaction[] | undefined>;
$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise<void>;
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise<void>;
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise<number | undefined>;
$replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise<modes.CommentThread | null>;
$editComment(handle: number, document: UriComponents, comment: modes.Comment, text: string): Promise<void>;
$deleteComment(handle: number, document: UriComponents, comment: modes.Comment): Promise<void>;

View File

@@ -99,6 +99,17 @@ export class ExtHostComments implements ExtHostCommentsShape {
return Promise.resolve(commentControllerHandle);
}
$onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number): Promise<number | undefined> {
const commentController = this._commentControllers.get(commentControllerHandle);
if (!commentController) {
return Promise.resolve(undefined);
}
commentController.$onActiveCommentThreadChange(threadHandle);
return Promise.resolve(threadHandle);
}
$provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<IRange[] | undefined> {
const commentController = this._commentControllers.get(commentControllerHandle);
@@ -146,28 +157,34 @@ export class ExtHostComments implements ExtHostCommentsShape {
});
}
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise<void> {
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise<number | undefined> {
const commentController = this._commentControllers.get(commentControllerHandle);
if (!commentController) {
return Promise.resolve();
return Promise.resolve(undefined);
}
if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread)) {
return Promise.resolve();
if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) && !(commentController.emptyCommentThreadFactory && commentController.emptyCommentThreadFactory.createEmptyCommentThread)) {
return Promise.resolve(undefined);
}
const document = this._documents.getDocument(URI.revive(uriComponents));
return asPromise(() => {
// TODO, remove this once GH PR stable deprecates `emptyCommentThreadFactory`.
if ((commentController as any).emptyCommentThreadFactory) {
return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range));
if (commentController.emptyCommentThreadFactory) {
return commentController.emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range));
}
if (commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) {
return commentController.commentingRangeProvider.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range));
}
}).then(() => Promise.resolve());
return undefined; // {{SQL CARBON EDIT}} @anthonydresser revert back after strict-null-check
}).then((commentThread: ExtHostCommentThread | undefined) => {
if (commentThread) {
return Promise.resolve(commentThread.handle);
}
return Promise.resolve(undefined);
});
}
registerWorkspaceCommentProvider(
@@ -451,6 +468,12 @@ export class ExtHostCommentThread implements vscode.CommentThread {
private _localDisposables: types.Disposable[];
private _isDiposed: boolean;
public get isDisposed(): boolean {
return this._isDiposed;
}
constructor(
private _proxy: MainThreadCommentsShape,
private readonly _commandsConverter: CommandsConverter,
@@ -469,6 +492,7 @@ export class ExtHostCommentThread implements vscode.CommentThread {
);
this._localDisposables = [];
this._isDiposed = false;
this._localDisposables.push(this.onDidUpdateCommentThread(() => {
this.eventuallyUpdateCommentThread();
@@ -519,6 +543,7 @@ export class ExtHostCommentThread implements vscode.CommentThread {
this._commentController.handle,
this.handle
);
this._isDiposed = true;
}
}
@@ -562,7 +587,17 @@ class ExtHostCommentController implements vscode.CommentController {
return this._label;
}
public inputBox?: ExtHostCommentInputBox;
public inputBox: ExtHostCommentInputBox | undefined;
private _activeCommentThread: ExtHostCommentThread | undefined;
public get activeCommentThread(): ExtHostCommentThread | undefined {
if (this._activeCommentThread && this._activeCommentThread.isDisposed) {
this._activeCommentThread = undefined;
}
return this._activeCommentThread;
}
public activeCommentingRange?: vscode.Range;
public get handle(): number {
@@ -570,7 +605,31 @@ class ExtHostCommentController implements vscode.CommentController {
}
private _threads: Map<number, ExtHostCommentThread> = new Map<number, ExtHostCommentThread>();
commentingRangeProvider?: vscode.CommentingRangeProvider;
commentingRangeProvider?: vscode.CommentingRangeProvider & { createEmptyCommentThread: (document: vscode.TextDocument, range: types.Range) => Promise<vscode.CommentThread>; };
private _emptyCommentThreadFactory: vscode.EmptyCommentThreadFactory | undefined;
get emptyCommentThreadFactory(): vscode.EmptyCommentThreadFactory | undefined {
return this._emptyCommentThreadFactory;
}
set emptyCommentThreadFactory(newEmptyCommentThreadFactory: vscode.EmptyCommentThreadFactory | undefined) {
this._emptyCommentThreadFactory = newEmptyCommentThreadFactory;
if (this._emptyCommentThreadFactory && this._emptyCommentThreadFactory.template) {
let template = this._emptyCommentThreadFactory.template;
const acceptInputCommand = template.acceptInputCommand ? this._commandsConverter.toInternal(template.acceptInputCommand) : undefined;
const additionalCommands = template.additionalCommands ? template.additionalCommands.map(x => this._commandsConverter.toInternal(x)) : [];
const deleteCommand = template.deleteCommand ? this._commandsConverter.toInternal(template.deleteCommand) : undefined;
this._proxy.$updateCommentControllerFeatures(this.handle, {
commentThreadTemplate: {
label: template.label,
acceptInputCommand,
additionalCommands,
deleteCommand
}
});
}
}
private _commentReactionProvider?: vscode.CommentReactionProvider;
@@ -610,6 +669,10 @@ class ExtHostCommentController implements vscode.CommentController {
}
}
$onActiveCommentThreadChange(threadHandle: number) {
this._activeCommentThread = this.getCommentThread(threadHandle);
}
getCommentThread(handle: number) {
return this._threads.get(handle);
}
@@ -664,6 +727,7 @@ function convertFromComment(comment: modes.Comment): vscode.Comment {
}
return {
id: comment.commentId,
commentId: comment.commentId,
body: extHostTypeConverter.MarkdownString.to(comment.body),
userName: comment.userName,
@@ -685,7 +749,7 @@ function convertToModeComment(commentController: ExtHostCommentController, vscod
const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar;
return {
commentId: vscodeComment.commentId,
commentId: vscodeComment.id || vscodeComment.commentId,
body: extHostTypeConverter.MarkdownString.from(vscodeComment.body),
userName: vscodeComment.userName,
userIconPath: iconPath,

View File

@@ -2287,6 +2287,61 @@ export enum FoldingRangeKind {
//#endregion
//#region Comment
@es5ClassCompat
export class Comment {
id: string;
body: MarkdownString;
userName: string;
label?: string;
userIconPath?: URI;
selectCommand?: vscode.Command;
editCommand?: vscode.Command;
deleteCommand?: vscode.Command;
/**
* The id of the comment
*
* @deprecated Use Id instead
*/
commentId: string;
/**
* @deprecated Use userIconPath instead. The avatar src of the user who created the comment
*/
gravatar?: string;
/**
* @deprecated, use editCommand
*/
canEdit?: boolean;
/**
* @deprecated, use deleteCommand
*/
canDelete?: boolean;
/**
* @deprecated
*/
command?: vscode.Command;
/**
* @deprecated
*/
isDraft?: boolean;
/**
* Proposed Comment Reaction
*/
commentReactions?: vscode.CommentReaction[];
constructor(id: string, body: MarkdownString, userName: string) {
this.id = id;
this.body = body;
this.userName = userName;
}
}
export enum CommentThreadCollapsibleState {
/**
@@ -2298,6 +2353,7 @@ export enum CommentThreadCollapsibleState {
*/
Expanded = 1
}
//#endregion
@es5ClassCompat
export class QuickInputButtons {

View File

@@ -780,6 +780,7 @@ export function createApiFactory(
Color: extHostTypes.Color,
ColorInformation: extHostTypes.ColorInformation,
ColorPresentation: extHostTypes.ColorPresentation,
Comment: extHostTypes.Comment,
CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState,
CompletionItem: extHostTypes.CompletionItem,
CompletionItemKind: extHostTypes.CompletionItemKind,

View File

@@ -309,17 +309,17 @@ export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusB
hc: Color.black.transparent(0.3),
}, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window."));
export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.hostBackground', {
dark: '#C40057',
light: '#C40057',
hc: '#C40057'
}, nls.localize('statusBarItemHostBackground', "Background color for the host indicator on the status bar."));
export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.remoteBackground', {
dark: '#16825D',
light: '#16825D',
hc: '#FFFFFF00'
}, nls.localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar."));
export const STATUS_BAR_HOST_NAME_FOREGROUND = registerColor('statusBarItem.hostForeground', {
export const STATUS_BAR_HOST_NAME_FOREGROUND = registerColor('statusBarItem.remoteForeground', {
dark: '#FFFFFF',
light: '#FFFFFF',
hc: '#FFFFFF'
}, nls.localize('statusBarItemHostForeground', "Foreground color for the host indicator on the status bar."));
}, nls.localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar."));
// < --- Activity Bar --- >
@@ -366,6 +366,18 @@ export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.for
hc: Color.white
}, nls.localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar."));
export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', {
dark: ACTIVITY_BAR_BADGE_BACKGROUND,
light: ACTIVITY_BAR_BADGE_BACKGROUND,
hc: ACTIVITY_BAR_BADGE_BACKGROUND
}, nls.localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view"));
export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', {
dark: ACTIVITY_BAR_BADGE_FOREGROUND,
light: ACTIVITY_BAR_BADGE_FOREGROUND,
hc: ACTIVITY_BAR_BADGE_FOREGROUND
}, nls.localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view"));
// < --- Side Bar --- >

View File

@@ -322,15 +322,17 @@ export class CommentService extends Disposable implements ICommentService {
}
}
let commentControlResult: Promise<ICommentInfo>[] = [];
let commentControlResult: Promise<ICommentInfo | null>[] = [];
this._commentControls.forEach(control => {
commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None));
commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None)
.catch(e => {
console.log(e);
return null;
}));
});
let ret = [...await Promise.all(result), ...await Promise.all(commentControlResult)];
return ret;
return Promise.all([...result, ...commentControlResult]);
}
async getCommentingRanges(resource: URI): Promise<IRange[]> {

View File

@@ -212,6 +212,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
} else {
const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand;
if (deleteCommand) {
this.commentService.setActiveCommentThread(this._commentThread);
return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || []));
}
}
@@ -239,7 +240,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
async update(commentThread: modes.CommentThread | modes.CommentThread2) {
async update(commentThread: modes.CommentThread | modes.CommentThread2, replaceTemplate: boolean = false) {
const oldCommentsLen = this._commentElements.length;
const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0;
@@ -288,12 +289,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentThread = commentThread;
this._commentElements = newCommentNodeList;
this.createThreadLabel();
this.createThreadLabel(replaceTemplate);
if (replaceTemplate) {
this.createTextModelListener();
}
if (this._formActions && this._commentEditor.hasModel()) {
dom.clearNode(this._formActions);
const model = this._commentEditor.getModel();
this.createCommentWidgetActions2(this._formActions, model);
if (replaceTemplate) {
this.createCommentWidgetActionsListener(this._formActions, model);
}
}
// Move comment glyph widget and show position if the line has changed.
@@ -348,7 +357,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
}
display(lineNumber: number) {
display(lineNumber: number, fromTemplate: boolean = false) {
this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber);
this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
@@ -386,58 +395,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentEditor.setModel(model);
this._disposables.push(this._commentEditor);
this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => this.setCommentEditorDecorations()));
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
this._disposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
let commentThread = this._commentThread as modes.CommentThread2;
commentThread.input = {
uri: this._commentEditor.getModel()!.uri,
value: this._commentEditor.getValue()
};
this.commentService.setActiveCommentThread(this._commentThread);
}));
this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = this._commentEditor.getValue();
let thread = (this._commentThread as modes.CommentThread2);
if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) {
let newInput: modes.CommentInput = thread.input;
newInput.value = modelContent;
thread.input = newInput;
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => {
let thread = (this._commentThread as modes.CommentThread2);
if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) {
return;
}
if (!input) {
return;
}
if (this._commentEditor.getValue() !== input.value) {
this._commentEditor.setValue(input.value);
if (input.value === '') {
this._pendingComment = '';
if (dom.hasClass(this._commentForm, 'expand')) {
dom.removeClass(this._commentForm, 'expand');
}
this._commentEditor.getDomNode()!.style.outline = '';
this._error.textContent = '';
dom.addClass(this._error, 'hidden');
}
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => {
await this.update(this._commentThread);
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => {
this.createThreadLabel();
}));
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined && !fromTemplate) {
this.createTextModelListener();
}
this.setCommentEditorDecorations();
@@ -456,50 +415,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._formActions = dom.append(this._commentForm, dom.$('.form-actions'));
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
this.createCommentWidgetActions2(this._formActions, model);
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => {
if (this._formActions) {
dom.clearNode(this._formActions);
this.createCommentWidgetActions2(this._formActions, model);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => {
if (this._formActions) {
dom.clearNode(this._formActions);
this.createCommentWidgetActions2(this._formActions, model);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => {
// Move comment glyph widget and show position if the line has changed.
const lineNumber = this._commentThread.range.startLineNumber;
let shouldMoveWidget = false;
if (this._commentGlyph) {
if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
shouldMoveWidget = true;
this._commentGlyph.setLineNumber(lineNumber);
}
}
if (shouldMoveWidget && this._isExpanded) {
this.show({ lineNumber, column: 1 }, 2);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => {
if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) {
const lineNumber = this._commentThread.range.startLineNumber;
this.show({ lineNumber, column: 1 }, 2);
return;
}
if (state === modes.CommentThreadCollapsibleState.Collapsed && this._isExpanded) {
this.hide();
return;
}
}));
if (!fromTemplate) {
this.createCommentWidgetActionsListener(this._formActions, model);
}
} else {
this.createCommentWidgetActions(this._formActions, model);
}
@@ -526,6 +444,106 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
private createTextModelListener() {
this._disposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
let commentThread = this._commentThread as modes.CommentThread2;
commentThread.input = {
uri: this._commentEditor.getModel()!.uri,
value: this._commentEditor.getValue()
};
this.commentService.setActiveCommentThread(this._commentThread);
}));
this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
let modelContent = this._commentEditor.getValue();
let thread = (this._commentThread as modes.CommentThread2);
if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) {
let newInput: modes.CommentInput = thread.input;
newInput.value = modelContent;
thread.input = newInput;
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => {
let thread = (this._commentThread as modes.CommentThread2);
if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) {
return;
}
if (!input) {
return;
}
if (this._commentEditor.getValue() !== input.value) {
this._commentEditor.setValue(input.value);
if (input.value === '') {
this._pendingComment = '';
if (dom.hasClass(this._commentForm, 'expand')) {
dom.removeClass(this._commentForm, 'expand');
}
this._commentEditor.getDomNode()!.style.outline = '';
this._error.textContent = '';
dom.addClass(this._error, 'hidden');
}
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => {
await this.update(this._commentThread);
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => {
this.createThreadLabel();
}));
}
private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) {
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => {
if (container) {
dom.clearNode(container);
this.createCommentWidgetActions2(container, model);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => {
if (container) {
dom.clearNode(container);
this.createCommentWidgetActions2(container, model);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => {
// Move comment glyph widget and show position if the line has changed.
const lineNumber = this._commentThread.range.startLineNumber;
let shouldMoveWidget = false;
if (this._commentGlyph) {
if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
shouldMoveWidget = true;
this._commentGlyph.setLineNumber(lineNumber);
}
}
if (shouldMoveWidget && this._isExpanded) {
this.show({ lineNumber, column: 1 }, 2);
}
}));
this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => {
if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) {
const lineNumber = this._commentThread.range.startLineNumber;
this.show({ lineNumber, column: 1 }, 2);
return;
}
if (state === modes.CommentThreadCollapsibleState.Collapsed && this._isExpanded) {
this.hide();
return;
}
}));
}
private handleError(e: Error) {
this._error.textContent = e.message;
this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
@@ -798,13 +816,14 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
private createThreadLabel() {
private createThreadLabel(replaceTemplate: boolean = false) {
let label: string | undefined;
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
label = (this._commentThread as modes.CommentThread2).label;
}
if (label === undefined) {
if (label === undefined && !replaceTemplate) {
// if it's for replacing the comment thread template, the comment thread widget title can be undefined as extensions may set it later
if (this._commentThread.comments && this._commentThread.comments.length) {
const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', ');
label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList);
@@ -813,8 +832,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
}
this._headingLabel.innerHTML = strings.escape(label);
this._headingLabel.setAttribute('aria-label', label);
if (label) {
this._headingLabel.innerHTML = strings.escape(label);
this._headingLabel.setAttribute('aria-label', label);
}
}
private expandReplyArea() {

View File

@@ -30,7 +30,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com
import { overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -64,7 +64,7 @@ class CommentingRangeDecoration {
return this._decorationId;
}
constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) {
constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private _template: modes.CommentThreadTemplate | undefined, private commentingRangesInfo?: modes.CommentingRanges) {
const startLineNumber = _range.startLineNumber;
const endLineNumber = _range.endLineNumber;
let commentingRangeDecorations = [{
@@ -81,13 +81,14 @@ class CommentingRangeDecoration {
}
}
public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } {
public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined } {
return {
extensionId: this._extensionId,
label: this._label,
replyCommand: this._reply,
ownerId: this._ownerId,
commentingRangesInfo: this.commentingRangesInfo
commentingRangesInfo: this.commentingRangesInfo,
template: this._template
};
}
@@ -124,11 +125,11 @@ class CommentingRangeDecorator {
for (const info of commentInfos) {
if (Array.isArray(info.commentingRanges)) {
info.commentingRanges.forEach(range => {
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions));
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions, info.template));
});
} else {
(info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => {
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, (info.commentingRanges as modes.CommentingRanges).newCommentThreadCommand, this.decorationOptions, info.commentingRanges as modes.CommentingRanges));
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, undefined, this.decorationOptions, info.template, info.commentingRanges as modes.CommentingRanges));
});
}
}
@@ -178,7 +179,6 @@ export class ReviewController implements IEditorContribution {
constructor(
editor: ICodeEditor,
@ICommentService private readonly commentService: ICommentService,
@ICommandService private readonly _commandService: ICommandService,
@INotificationService private readonly notificationService: INotificationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@@ -441,6 +441,11 @@ export class ReviewController implements IEditorContribution {
}
});
added.forEach(thread => {
let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
if (matchedZones.length) {
return;
}
const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId];
this.displayCommentThread(e.owner, thread, pendingCommentText, draftMode);
this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
@@ -457,6 +462,30 @@ export class ReviewController implements IEditorContribution {
this._commentWidgets.push(zoneWidget);
}
private addCommentThreadFromTemplate(lineNumber: number, ownerId: string, extensionId: string | undefined, template: modes.CommentThreadTemplate): ReviewZoneWidget {
let templateReviewZoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, {
commentThreadHandle: -1,
label: template!.label,
acceptInputCommand: template.acceptInputCommand,
additionalCommands: template.additionalCommands,
deleteCommand: template.deleteCommand,
extensionId: extensionId,
threadId: null,
resource: null,
comments: [],
range: {
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: 0
},
collapsibleState: modes.CommentThreadCollapsibleState.Expanded,
},
'', modes.DraftMode.NotSupported);
return templateReviewZoneWidget;
}
private addComment(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, draftMode: modes.DraftMode | undefined, pendingComment: string | null) {
if (this._newCommentWidget) {
this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position ? this._newCommentWidget.position.lineNumber : -1} before creating a new one.`);
@@ -612,16 +641,16 @@ export class ReviewController implements IEditorContribution {
const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id);
if (commentInfos.length) {
const { replyCommand, ownerId, extensionId, commentingRangesInfo } = commentInfos[0];
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo);
const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = commentInfos[0];
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template);
}
}).then(() => {
this._addInProgress = false;
});
}
} else {
const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfos[0]!;
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo);
const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = newCommentInfos[0]!;
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template);
}
return Promise.resolve();
@@ -640,11 +669,11 @@ export class ReviewController implements IEditorContribution {
return picks;
}
private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] {
private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] {
const actions: (IAction | ContextSubMenu)[] = [];
commentInfos.forEach(commentInfo => {
const { replyCommand, ownerId, extensionId, label, commentingRangesInfo } = commentInfo;
const { replyCommand, ownerId, extensionId, label, commentingRangesInfo, template } = commentInfo;
actions.push(new Action(
'addCommentThread',
@@ -652,7 +681,7 @@ export class ReviewController implements IEditorContribution {
undefined,
true,
() => {
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo);
this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template);
return Promise.resolve();
}
));
@@ -660,17 +689,25 @@ export class ReviewController implements IEditorContribution {
return actions;
}
public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined) {
public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined) {
if (commentingRangesInfo) {
let range = new Range(lineNumber, 1, lineNumber, 1);
if (commentingRangesInfo.newCommentThreadCommand) {
if (replyCommand) {
const commandId = replyCommand.id;
const args = replyCommand.arguments || [];
if (commentingRangesInfo.newCommentThreadCallback && template) {
// create comment widget through template
let commentThreadWidget = this.addCommentThreadFromTemplate(lineNumber, ownerId, extensionId, template);
commentThreadWidget.display(lineNumber, true);
this._commandService.executeCommand(commandId, ...args);
this._addInProgress = false;
}
return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range)
.then(commentThread => {
commentThreadWidget.update(commentThread!, true);
this._commentWidgets.push(commentThreadWidget);
this.processNextThreadToAdd();
})
.catch(e => {
this.notificationService.error(nls.localize('commentThreadAddFailure', "Adding a new comment thread failed: {0}.", e.message));
commentThreadWidget.dispose();
this.processNextThreadToAdd();
});
} else if (commentingRangesInfo.newCommentThreadCallback) {
return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range)
.then(_ => {

View File

@@ -13,7 +13,7 @@ import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/pla
import { ILabelService } from 'vs/platform/label/common/label';
import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction, ReloadAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { Emitter, Event } from 'vs/base/common/event';
@@ -323,8 +323,8 @@ class RemoteBadge extends Disposable {
if (!this.element) {
return;
}
const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND);
const fgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_FOREGROUND);
const bgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND);
const fgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND);
this.element.style.backgroundColor = bgColor ? bgColor.toString() : '';
this.element.style.color = fgColor ? fgColor.toString() : '';
};

View File

@@ -19,7 +19,7 @@ import { ResourceMap } from 'vs/base/common/map';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { FileOnDiskContentProvider } from 'vs/workbench/contrib/files/common/files';
import { FileOnDiskContentProvider, resourceToFileOnDisk } from 'vs/workbench/contrib/files/common/files';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { IModelService } from 'vs/editor/common/services/modelService';
import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands';
@@ -248,7 +248,7 @@ class ResolveSaveConflictAction extends Action {
return this.editorService.openEditor(
{
leftResource: resource.with({ scheme: CONFLICT_RESOLUTION_SCHEME }),
leftResource: resourceToFileOnDisk(CONFLICT_RESOLUTION_SCHEME, resource),
rightResource: resource,
label: editorLabel,
options: { pinned: true }

View File

@@ -23,8 +23,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { once } from 'vs/base/common/functional';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { toLocalResource } from 'vs/base/common/resources';
/**
* Explorer viewlet id.
@@ -133,6 +131,14 @@ export const SortOrderConfiguration = {
export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified';
export function resourceToFileOnDisk(scheme: string, resource: URI): URI {
return resource.with({ scheme, query: JSON.stringify({ scheme: resource.scheme }) });
}
export function fileOnDiskToResource(resource: URI): URI {
return resource.with({ scheme: JSON.parse(resource.query)['scheme'], query: null });
}
export class FileOnDiskContentProvider implements ITextModelContentProvider {
private fileWatcherDisposable: IDisposable | undefined;
@@ -140,13 +146,12 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
@ITextFileService private readonly textFileService: ITextFileService,
@IFileService private readonly fileService: IFileService,
@IModeService private readonly modeService: IModeService,
@IModelService private readonly modelService: IModelService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
@IModelService private readonly modelService: IModelService
) {
}
provideTextContent(resource: URI): Promise<ITextModel> {
const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority);
const savedFileResource = fileOnDiskToResource(resource);
// Make sure our file from disk is resolved up to date
return this.resolveEditorModel(resource).then(codeEditorModel => {
@@ -174,7 +179,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
private resolveEditorModel(resource: URI, createAsNeeded?: true): Promise<ITextModel>;
private resolveEditorModel(resource: URI, createAsNeeded?: boolean): Promise<ITextModel | null>;
private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise<ITextModel | null> {
const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority);
const savedFileResource = fileOnDiskToResource(resource);
return this.textFileService.readStream(savedFileResource).then(content => {
let codeEditorModel = this.modelService.getModel(resource);

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileOnDiskContentProvider, resourceToFileOnDisk } from 'vs/workbench/contrib/files/common/files';
import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
class ServiceAccessor {
constructor(
@IFileService public fileService: TestFileService
) {
}
}
suite('Files - FileOnDiskContentProvider', () => {
let instantiationService: IInstantiationService;
let accessor: ServiceAccessor;
setup(() => {
instantiationService = workbenchInstantiationService();
accessor = instantiationService.createInstance(ServiceAccessor);
});
test('provideTextContent', async () => {
const provider = instantiationService.createInstance(FileOnDiskContentProvider);
const uri = URI.parse('testFileOnDiskContentProvider://foo');
const content = await provider.provideTextContent(resourceToFileOnDisk('conflictResolution', uri));
assert.equal(snapshotToString(content.createSnapshot()), 'Hello Html');
assert.equal(accessor.fileService.getLastReadFileUri().toString(), uri.toString());
});
});

View File

@@ -469,7 +469,7 @@ export class RemoteFileDialog {
this.autoCompletePathSegment = '';
return false;
}
const itemBasename = quickPickItem.label;
const itemBasename = this.trimTrailingSlash(quickPickItem.label);
// Either force the autocomplete, or the old value should be one smaller than the new value and match the new value.
if (itemBasename === '..') {
// Don't match on the up directory item ever.

View File

@@ -899,6 +899,7 @@ export class TestFileService implements IFileService {
readonly onError: Event<Error> = Event.None;
private content = 'Hello Html';
private lastReadFileUri: URI;
constructor() {
this._onFileChanges = new Emitter<FileChangesEvent>();
@@ -913,6 +914,10 @@ export class TestFileService implements IFileService {
return this.content;
}
public getLastReadFileUri(): URI {
return this.lastReadFileUri;
}
public get onFileChanges(): Event<FileChangesEvent> {
return this._onFileChanges.event;
}
@@ -952,6 +957,8 @@ export class TestFileService implements IFileService {
}
readFile(resource: URI, options?: IReadFileOptions | undefined): Promise<IFileContent> {
this.lastReadFileUri = resource;
return Promise.resolve({
resource: resource,
value: VSBuffer.fromString(this.content),
@@ -964,6 +971,8 @@ export class TestFileService implements IFileService {
}
readFileStream(resource: URI, options?: IReadFileOptions | undefined): Promise<IFileStreamContent> {
this.lastReadFileUri = resource;
return Promise.resolve({
resource: resource,
value: {