Merge from vscode 777931080477e28b7c27e8f7d4b0d69897945946 (#9220)

This commit is contained in:
Anthony Dresser
2020-02-19 22:27:53 -08:00
committed by GitHub
parent ab6fb810f8
commit 0cec223301
115 changed files with 1431 additions and 1133 deletions
@@ -7,6 +7,7 @@ import { getLocation, parse, visit } from 'jsonc-parser';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { SettingsDocument } from './settingsDocumentHelper'; import { SettingsDocument } from './settingsDocumentHelper';
import { provideInstalledExtensionProposals } from './extensionsProposals';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export function activate(context: vscode.ExtensionContext): void { export function activate(context: vscode.ExtensionContext): void {
@@ -80,7 +81,7 @@ function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable
const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
if (location.path[0] === 'recommendations') { if (location.path[0] === 'recommendations') {
const extensionsContent = <IExtensionsContent>parse(document.getText()); const extensionsContent = <IExtensionsContent>parse(document.getText());
return provideInstalledExtensionProposals(extensionsContent, range); return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false);
} }
return []; return [];
} }
@@ -94,41 +95,13 @@ function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode
const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') { if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') {
const extensionsContent = <IExtensionsContent>parse(document.getText())['extensions']; const extensionsContent = <IExtensionsContent>parse(document.getText())['extensions'];
return provideInstalledExtensionProposals(extensionsContent, range); return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], range, false);
} }
return []; return [];
} }
}); });
} }
function provideInstalledExtensionProposals(extensionsContent: IExtensionsContent, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const alreadyEnteredExtensions = extensionsContent && extensionsContent.recommendations || [];
if (Array.isArray(alreadyEnteredExtensions)) {
const knownExtensionProposals = vscode.extensions.all.filter(e =>
!(e.id.startsWith('vscode.')
|| e.id === 'Microsoft.vscode-markdown'
|| alreadyEnteredExtensions.indexOf(e.id) > -1));
if (knownExtensionProposals.length) {
return knownExtensionProposals.map(e => {
const item = new vscode.CompletionItem(e.id);
const insertText = `"${e.id}"`;
item.kind = vscode.CompletionItemKind.Value;
item.insertText = insertText;
item.range = range;
item.filterText = insertText;
return item;
});
} else {
const example = new vscode.CompletionItem(localize('exampleExtension', "Example"));
example.insertText = '"vscode.csharp"';
example.kind = vscode.CompletionItemKind.Value;
example.range = range;
return [example];
}
}
return undefined;
}
vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, {
provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> {
const result: vscode.SymbolInformation[] = []; const result: vscode.SymbolInformation[] = [];
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export function provideInstalledExtensionProposals(existing: string[], range: vscode.Range, includeBuiltinExtensions: boolean): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
if (Array.isArray(existing)) {
const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown'));
const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1);
if (knownExtensionProposals.length) {
return knownExtensionProposals.map(e => {
const item = new vscode.CompletionItem(e.id);
const insertText = `"${e.id}"`;
item.kind = vscode.CompletionItemKind.Value;
item.insertText = insertText;
item.range = range;
item.filterText = insertText;
return item;
});
} else {
const example = new vscode.CompletionItem(localize('exampleExtension', "Example"));
example.insertText = '"vscode.csharp"';
example.kind = vscode.CompletionItemKind.Value;
example.range = range;
return [example];
}
}
return undefined;
}
@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getLocation, Location } from 'jsonc-parser'; import { getLocation, Location, parse } from 'jsonc-parser';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { provideInstalledExtensionProposals } from './extensionsProposals';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -13,7 +14,7 @@ export class SettingsDocument {
constructor(private document: vscode.TextDocument) { } constructor(private document: vscode.TextDocument) { }
public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem[]> { public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const location = getLocation(this.document.getText(), this.document.offsetAt(position)); const location = getLocation(this.document.getText(), this.document.offsetAt(position));
const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position); const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
@@ -41,6 +42,15 @@ export class SettingsDocument {
}); });
} }
// sync.ignoredExtensions
if (location.path[0] === 'sync.ignoredExtensions') {
let ignoredExtensions = [];
try {
ignoredExtensions = parse(this.document.getText())['sync.ignoredExtensions'];
} catch (e) {/* ignore error */ }
return provideInstalledExtensionProposals(ignoredExtensions, range, true);
}
return this.provideLanguageOverridesCompletionItems(location, position); return this.provideLanguageOverridesCompletionItems(location, position);
} }
+10 -1
View File
@@ -1847,7 +1847,16 @@
{ {
"view": "workbench.scm", "view": "workbench.scm",
"contents": "%view.workbench.scm.workspace%", "contents": "%view.workbench.scm.workspace%",
"when": "config.git.enabled && !git.missing && workbenchState == workspace" "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0"
},
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.emptyWorkspace%",
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0"
},
{
"view": "workbench.explorer.emptyView",
"contents": "%view.workbench.cloneRepository%"
} }
] ]
}, },
+5 -3
View File
@@ -152,7 +152,9 @@
"colors.submodule": "Color for submodule resources.", "colors.submodule": "Color for submodule resources.",
"view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.", "view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.",
"view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone Repository](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).", "view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init?%5Btrue%5D)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)." "view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.emptyWorkspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Add Folder to Workspace](command:workbench.action.addRootFolder)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.cloneRepository": "You can also clone a repository from a URL. To learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).\n[Clone Repository](command:git.clone)"
} }
+18 -13
View File
@@ -566,24 +566,29 @@ export class CommandCenter {
} }
@command('git.init') @command('git.init')
async init(): Promise<void> { async init(skipFolderPrompt = false): Promise<void> {
let repositoryPath: string | undefined = undefined; let repositoryPath: string | undefined = undefined;
let askToOpen = true; let askToOpen = true;
if (workspace.workspaceFolders) { if (workspace.workspaceFolders) {
const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); if (skipFolderPrompt && workspace.workspaceFolders.length === 1) {
const pick = { label: localize('choose', "Choose Folder...") }; repositoryPath = workspace.workspaceFolders[0].uri.fsPath;
const items: { label: string, folder?: WorkspaceFolder }[] = [
...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })),
pick
];
const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });
if (!item) {
return;
} else if (item.folder) {
repositoryPath = item.folder.uri.fsPath;
askToOpen = false; askToOpen = false;
} else {
const placeHolder = localize('init', "Pick workspace folder to initialize git repo in");
const pick = { label: localize('choose', "Choose Folder...") };
const items: { label: string, folder?: WorkspaceFolder }[] = [
...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })),
pick
];
const item = await window.showQuickPick(items, { placeHolder, ignoreFocusOut: true });
if (!item) {
return;
} else if (item.folder) {
repositoryPath = item.folder.uri.fsPath;
askToOpen = false;
}
} }
} }
+16 -2
View File
@@ -1,8 +1,8 @@
{ {
"name": "vscode-account", "name": "vscode-account",
"publisher": "vscode", "publisher": "vscode",
"displayName": "Account", "displayName": "%displayName%",
"description": "", "description": "%description%",
"version": "0.0.1", "version": "0.0.1",
"engines": { "engines": {
"vscode": "^1.42.0" "vscode": "^1.42.0"
@@ -15,6 +15,20 @@
"*" "*"
], ],
"main": "./out/extension.js", "main": "./out/extension.js",
"contributes": {
"commands": [
{
"command": "microsoft.signin",
"title": "%signIn%",
"category": "%displayName%"
},
{
"command": "microsoft.signout",
"title": "%signOut%",
"category": "%displayName%"
}
]
},
"scripts": { "scripts": {
"vscode:prepublish": "npm run compile", "vscode:prepublish": "npm run compile",
"compile": "gulp compile-extension:vscode-account", "compile": "gulp compile-extension:vscode-account",
@@ -0,0 +1,6 @@
{
"displayName": "Microsoft Account",
"description": "Microsoft authentication provider",
"signIn": "Sign in",
"signOut": "Sign out"
}
+35 -3
View File
@@ -6,13 +6,15 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper';
export async function activate(_: vscode.ExtensionContext) { export const DEFAULT_SCOPES = 'https://management.core.windows.net/.default offline_access';
export async function activate(context: vscode.ExtensionContext) {
const loginService = new AzureActiveDirectoryService(); const loginService = new AzureActiveDirectoryService();
await loginService.initialize(); await loginService.initialize();
vscode.authentication.registerAuthenticationProvider({ context.subscriptions.push(vscode.authentication.registerAuthenticationProvider({
id: 'MSA', id: 'MSA',
displayName: 'Microsoft', displayName: 'Microsoft',
onDidChangeSessions: onDidChangeSessions.event, onDidChangeSessions: onDidChangeSessions.event,
@@ -28,7 +30,37 @@ export async function activate(_: vscode.ExtensionContext) {
logout: async (id: string) => { logout: async (id: string) => {
return loginService.logout(id); return loginService.logout(id);
} }
}); }));
context.subscriptions.push(vscode.commands.registerCommand('microsoft.signin', () => {
return loginService.login(DEFAULT_SCOPES);
}));
context.subscriptions.push(vscode.commands.registerCommand('microsoft.signout', async () => {
const sessions = loginService.sessions;
if (sessions.length === 0) {
return;
}
if (sessions.length === 1) {
await loginService.logout(loginService.sessions[0].id);
onDidChangeSessions.fire();
return;
}
const selectedSession = await vscode.window.showQuickPick(sessions.map(session => {
return {
id: session.id,
label: session.accountName
};
}));
if (selectedSession) {
await loginService.logout(selectedSession.id);
onDidChangeSessions.fire();
return;
}
}));
return; return;
} }
@@ -5,7 +5,7 @@
@font-face { @font-face {
font-family: "codicon"; font-family: "codicon";
src: url("./codicon.ttf?d0510f6ecacbb2788db2b3162273a3d8") format("truetype"); src: url("./codicon.ttf?279add2ec8b3d516ca20a123230cbf9f") format("truetype");
} }
.codicon[class*='codicon-'] { .codicon[class*='codicon-'] {
@@ -303,6 +303,7 @@
.codicon-paintcan:before { content: "\eb2a" } .codicon-paintcan:before { content: "\eb2a" }
.codicon-pin:before { content: "\eb2b" } .codicon-pin:before { content: "\eb2b" }
.codicon-play:before { content: "\eb2c" } .codicon-play:before { content: "\eb2c" }
.codicon-run:before { content: "\eb2c" }
.codicon-plug:before { content: "\eb2d" } .codicon-plug:before { content: "\eb2d" }
.codicon-preserve-case:before { content: "\eb2e" } .codicon-preserve-case:before { content: "\eb2e" }
.codicon-preview:before { content: "\eb2f" } .codicon-preview:before { content: "\eb2f" }
@@ -413,5 +414,6 @@
.codicon-feedback:before { content: "\eb96" } .codicon-feedback:before { content: "\eb96" }
.codicon-group-by-ref-type:before { content: "\eb97" } .codicon-group-by-ref-type:before { content: "\eb97" }
.codicon-ungroup-by-ref-type:before { content: "\eb98" } .codicon-ungroup-by-ref-type:before { content: "\eb98" }
.codicon-debug-alt-2:before { content: "\f101" } .codicon-bell-dot:before { content: "\f101" }
.codicon-debug-alt:before { content: "\f102" } .codicon-debug-alt-2:before { content: "\f102" }
.codicon-debug-alt:before { content: "\f103" }
+8 -1
View File
@@ -52,6 +52,7 @@ export interface IListViewOptions<T> {
readonly useShadows?: boolean; readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility; readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean; readonly setRowLineHeight?: boolean;
readonly setRowHeight?: boolean;
readonly supportDynamicHeights?: boolean; readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean; readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean; readonly horizontalScrolling?: boolean;
@@ -63,6 +64,7 @@ const DefaultOptions = {
useShadows: true, useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto, verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true, setRowLineHeight: true,
setRowHeight: true,
supportDynamicHeights: false, supportDynamicHeights: false,
dnd: { dnd: {
getDragElements<T>(e: T) { return [e]; }, getDragElements<T>(e: T) { return [e]; },
@@ -174,6 +176,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private dragOverAnimationStopDisposable: IDisposable = Disposable.None; private dragOverAnimationStopDisposable: IDisposable = Disposable.None;
private dragOverMouseY: number = 0; private dragOverMouseY: number = 0;
private setRowLineHeight: boolean; private setRowLineHeight: boolean;
private setRowHeight: boolean;
private supportDynamicHeights: boolean; private supportDynamicHeights: boolean;
private horizontalScrolling: boolean; private horizontalScrolling: boolean;
private additionalScrollHeight: number; private additionalScrollHeight: number;
@@ -262,6 +265,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables); domEvent(window, 'dragend')(this.onDragEnd, this, this.disposables);
this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight); this.setRowLineHeight = getOrDefault(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.setRowHeight = getOrDefault(options, o => o.setRowHeight, DefaultOptions.setRowHeight);
this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights); this.supportDynamicHeights = getOrDefault(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd); this.dnd = getOrDefault<IListViewOptions<T>, IListViewDragAndDrop<T>>(options, o => o.dnd, DefaultOptions.dnd);
@@ -614,7 +618,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private updateItemInDOM(item: IItem<T>, index: number): void { private updateItemInDOM(item: IItem<T>, index: number): void {
item.row!.domNode!.style.top = `${this.elementTop(index)}px`; item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
item.row!.domNode!.style.height = `${item.size}px`;
if (this.setRowHeight) {
item.row!.domNode!.style.height = `${item.size}px`;
}
if (this.setRowLineHeight) { if (this.setRowLineHeight) {
item.row!.domNode!.style.lineHeight = `${item.size}px`; item.row!.domNode!.style.lineHeight = `${item.size}px`;
@@ -847,6 +847,7 @@ export interface IListOptions<T> {
readonly useShadows?: boolean; readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility; readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean; readonly setRowLineHeight?: boolean;
readonly setRowHeight?: boolean;
readonly supportDynamicHeights?: boolean; readonly supportDynamicHeights?: boolean;
readonly mouseSupport?: boolean; readonly mouseSupport?: boolean;
readonly horizontalScrolling?: boolean; readonly horizontalScrolling?: boolean;
@@ -453,8 +453,7 @@ export class QuickInputList {
if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) { if ((what === 'Previous' || what === 'PreviousPage') && this.list.getFocus()[0] === 0) {
what = 'Last'; what = 'Last';
} }
this.list['focus' + what as 'focusFirst' | 'focusLast' | 'focusNext' | 'focusPrevious' | 'focusNextPage' | 'focusPreviousPage']();
(this.list as any)['focus' + what]();
this.list.reveal(this.list.getFocus()[0]); this.list.reveal(this.list.getFocus()[0]);
} }
+2 -2
View File
@@ -677,8 +677,8 @@ export class CodeApplication extends Disposable {
const noRecentEntry = args['skip-add-to-recently-opened'] === true; const noRecentEntry = args['skip-add-to-recently-opened'] === true;
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
// new window if "-n" was used without paths // new window if "-n" or "--remote" was used without paths
if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { if ((args['new-window'] || args.remote) && !hasCliArgs && !hasFolderURIs && !hasFileURIs) {
return windowsMainService.open({ return windowsMainService.open({
context, context,
cli: args, cli: args,
@@ -86,7 +86,7 @@ class VisualEditorState {
constructor( constructor(
private _contextMenuService: IContextMenuService, private _contextMenuService: IContextMenuService,
private _clipboardService: IClipboardService | null private _clipboardService: IClipboardService
) { ) {
this._zones = []; this._zones = [];
this.inlineDiffMargins = []; this.inlineDiffMargins = [];
@@ -136,7 +136,7 @@ class VisualEditorState {
this._zones.push(zoneId); this._zones.push(zoneId);
this._zonesMap[String(zoneId)] = true; this._zonesMap[String(zoneId)] = true;
if (newDecorations.zones[i].diff && viewZone.marginDomNode && this._clipboardService) { if (newDecorations.zones[i].diff && viewZone.marginDomNode) {
viewZone.suppressMouseDown = false; viewZone.suppressMouseDown = false;
this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService)); this.inlineDiffMargins.push(new InlineDiffMargin(zoneId, viewZone.marginDomNode, editor, newDecorations.zones[i].diff!, this._contextMenuService, this._clipboardService));
} }
@@ -223,7 +223,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
constructor( constructor(
domElement: HTMLElement, domElement: HTMLElement,
options: IDiffEditorOptions, options: IDiffEditorOptions,
clipboardService: IClipboardService | null, @IClipboardService clipboardService: IClipboardService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService, @IEditorWorkerService editorWorkerService: IEditorWorkerService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
+12 -11
View File
@@ -12,10 +12,10 @@ import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperat
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations'; import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range, IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon'; import * as editorCommon from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer } from 'vs/editor/common/model'; import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents'; import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import * as viewEvents from 'vs/editor/common/view/viewEvents'; import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
@@ -903,8 +903,8 @@ class CommandExecutor {
if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) { if (commandsData.hadTrackedEditOperation && filteredOperations.length > 0) {
filteredOperations[0]._isTracked = true; filteredOperations[0]._isTracked = true;
} }
let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] => { let selectionsAfter = ctx.model.pushEditOperations(ctx.selectionsBefore, filteredOperations, (inverseEditOperations: IValidEditOperation[]): Selection[] => {
let groupedInverseEditOperations: IIdentifiedSingleEditOperation[][] = []; let groupedInverseEditOperations: IValidEditOperation[][] = [];
for (let i = 0; i < ctx.selectionsBefore.length; i++) { for (let i = 0; i < ctx.selectionsBefore.length; i++) {
groupedInverseEditOperations[i] = []; groupedInverseEditOperations[i] = [];
} }
@@ -915,7 +915,7 @@ class CommandExecutor {
} }
groupedInverseEditOperations[op.identifier.major].push(op); groupedInverseEditOperations[op.identifier.major].push(op);
} }
const minorBasedSorter = (a: IIdentifiedSingleEditOperation, b: IIdentifiedSingleEditOperation) => { const minorBasedSorter = (a: IValidEditOperation, b: IValidEditOperation) => {
return a.identifier!.minor - b.identifier!.minor; return a.identifier!.minor - b.identifier!.minor;
}; };
let cursorSelections: Selection[] = []; let cursorSelections: Selection[] = [];
@@ -1000,8 +1000,8 @@ class CommandExecutor {
let operations: IIdentifiedSingleEditOperation[] = []; let operations: IIdentifiedSingleEditOperation[] = [];
let operationMinor = 0; let operationMinor = 0;
const addEditOperation = (selection: Range, text: string | null, forceMoveMarkers: boolean = false) => { const addEditOperation = (range: IRange, text: string | null, forceMoveMarkers: boolean = false) => {
if (selection.isEmpty() && text === '') { if (Range.isEmpty(range) && text === '') {
// This command wants to add a no-op => no thank you // This command wants to add a no-op => no thank you
return; return;
} }
@@ -1010,7 +1010,7 @@ class CommandExecutor {
major: majorIdentifier, major: majorIdentifier,
minor: operationMinor++ minor: operationMinor++
}, },
range: selection, range: range,
text: text, text: text,
forceMoveMarkers: forceMoveMarkers, forceMoveMarkers: forceMoveMarkers,
isAutoWhitespaceEdit: command.insertsAutoWhitespace isAutoWhitespaceEdit: command.insertsAutoWhitespace
@@ -1018,12 +1018,13 @@ class CommandExecutor {
}; };
let hadTrackedEditOperation = false; let hadTrackedEditOperation = false;
const addTrackedEditOperation = (selection: Range, text: string | null, forceMoveMarkers?: boolean) => { const addTrackedEditOperation = (selection: IRange, text: string | null, forceMoveMarkers?: boolean) => {
hadTrackedEditOperation = true; hadTrackedEditOperation = true;
addEditOperation(selection, text, forceMoveMarkers); addEditOperation(selection, text, forceMoveMarkers);
}; };
const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => { const trackSelection = (_selection: ISelection, trackPreviousOnEmpty?: boolean) => {
const selection = Selection.liftSelection(_selection);
let stickiness: TrackedRangeStickiness; let stickiness: TrackedRangeStickiness;
if (selection.isEmpty()) { if (selection.isEmpty()) {
if (typeof trackPreviousOnEmpty === 'boolean') { if (typeof trackPreviousOnEmpty === 'boolean') {
@@ -1093,7 +1094,7 @@ class CommandExecutor {
const previousOp = operations[i - 1]; const previousOp = operations[i - 1];
const currentOp = operations[i]; const currentOp = operations[i];
if (previousOp.range.getStartPosition().isBefore(currentOp.range.getEndPosition())) { if (Range.getStartPosition(previousOp.range).isBefore(Range.getEndPosition(currentOp.range))) {
let loserMajor: number; let loserMajor: number;
+16 -2
View File
@@ -264,14 +264,28 @@ export class Range {
* Return the end position (which will be after or equal to the start position) * Return the end position (which will be after or equal to the start position)
*/ */
public getEndPosition(): Position { public getEndPosition(): Position {
return new Position(this.endLineNumber, this.endColumn); return Range.getEndPosition(this);
}
/**
* Return the end position (which will be after or equal to the start position)
*/
public static getEndPosition(range: IRange): Position {
return new Position(range.endLineNumber, range.endColumn);
} }
/** /**
* Return the start position (which will be before or equal to the end position) * Return the start position (which will be before or equal to the end position)
*/ */
public getStartPosition(): Position { public getStartPosition(): Position {
return new Position(this.startLineNumber, this.startColumn); return Range.getStartPosition(this);
}
/**
* Return the start position (which will be before or equal to the end position)
*/
public static getStartPosition(range: IRange): Position {
return new Position(range.startLineNumber, range.startColumn);
} }
/** /**
+4 -4
View File
@@ -10,7 +10,7 @@ import { ConfigurationChangedEvent, IComputedEditorOptions, IEditorOptions } fro
import { IPosition, Position } from 'vs/editor/common/core/position'; import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness, IValidEditOperation } from 'vs/editor/common/model';
import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { ThemeColor } from 'vs/platform/theme/common/themeService';
/** /**
@@ -22,7 +22,7 @@ export interface IEditOperationBuilder {
* @param range The range to replace (delete). May be empty to represent a simple insert. * @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete. * @param text The text to replace with. May be null to represent a simple delete.
*/ */
addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
/** /**
* Add a new edit operation (a replace operation). * Add a new edit operation (a replace operation).
@@ -30,7 +30,7 @@ export interface IEditOperationBuilder {
* @param range The range to replace (delete). May be empty to represent a simple insert. * @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete. * @param text The text to replace with. May be null to represent a simple delete.
*/ */
addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
/** /**
* Track `selection` when applying edit operations. * Track `selection` when applying edit operations.
@@ -51,7 +51,7 @@ export interface ICursorStateComputerData {
/** /**
* Get the inverse edit operations of the added edit operations. * Get the inverse edit operations of the added edit operations.
*/ */
getInverseEditOperations(): IIdentifiedSingleEditOperation[]; getInverseEditOperations(): IValidEditOperation[];
/** /**
* Get a previously tracked selection. * Get a previously tracked selection.
* @param id The unique identifier returned by `trackSelection`. * @param id The unique identifier returned by `trackSelection`.
+40 -5
View File
@@ -335,7 +335,7 @@ export interface IIdentifiedSingleEditOperation {
/** /**
* The range to replace. This can be empty to emulate a simple insert. * The range to replace. This can be empty to emulate a simple insert.
*/ */
range: Range; range: IRange;
/** /**
* The text to replace with. This can be null to emulate a simple delete. * The text to replace with. This can be null to emulate a simple delete.
*/ */
@@ -358,6 +358,27 @@ export interface IIdentifiedSingleEditOperation {
_isTracked?: boolean; _isTracked?: boolean;
} }
export interface IValidEditOperation {
/**
* An identifier associated with this single edit operation.
* @internal
*/
identifier: ISingleEditOperationIdentifier | null;
/**
* The range to replace. This can be empty to emulate a simple insert.
*/
range: Range;
/**
* The text to replace with. This can be null to emulate a simple delete.
*/
text: string | null;
/**
* This indicates that this operation has "insert" semantics.
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
*/
forceMoveMarkers: boolean;
}
/** /**
* A callback that can compute the cursor state after applying a series of edit operations. * A callback that can compute the cursor state after applying a series of edit operations.
*/ */
@@ -365,7 +386,7 @@ export interface ICursorStateComputer {
/** /**
* A callback that can compute the resulting cursors state after some edit operations have been executed. * A callback that can compute the resulting cursors state after some edit operations have been executed.
*/ */
(inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; (inverseEditOperations: IValidEditOperation[]): Selection[] | null;
} }
export class TextModelResolvedOptions { export class TextModelResolvedOptions {
@@ -1063,7 +1084,7 @@ export interface ITextModel {
* @param operations The edit operations. * @param operations The edit operations.
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state. * @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
*/ */
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[]; applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
/** /**
* Change the end of line sequence without recording in the undo stack. * Change the end of line sequence without recording in the undo stack.
@@ -1206,6 +1227,20 @@ export const enum ModelConstants {
FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000 FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000
} }
/**
* @internal
*/
export class ValidAnnotatedEditOperation implements IIdentifiedSingleEditOperation {
constructor(
public readonly identifier: ISingleEditOperationIdentifier | null,
public readonly range: Range,
public readonly text: string | null,
public readonly forceMoveMarkers: boolean,
public readonly isAutoWhitespaceEdit: boolean,
public readonly _isTracked: boolean,
) { }
}
/** /**
* @internal * @internal
*/ */
@@ -1234,7 +1269,7 @@ export interface ITextBuffer {
getLineLastNonWhitespaceColumn(lineNumber: number): number; getLineLastNonWhitespaceColumn(lineNumber: number): number;
setEOL(newEOL: '\r\n' | '\n'): void; setEOL(newEOL: '\r\n' | '\n'): void;
applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[]; findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
} }
@@ -1244,7 +1279,7 @@ export interface ITextBuffer {
export class ApplyEditsResult { export class ApplyEditsResult {
constructor( constructor(
public readonly reverseEdits: IIdentifiedSingleEditOperation[], public readonly reverseEdits: IValidEditOperation[],
public readonly changes: IInternalModelContentChange[], public readonly changes: IInternalModelContentChange[],
public readonly trimAutoWhitespaceLineNumbers: number[] | null public readonly trimAutoWhitespaceLineNumbers: number[] | null
) { } ) { }
+3 -3
View File
@@ -5,11 +5,11 @@
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
interface IEditOperation { interface IEditOperation {
operations: IIdentifiedSingleEditOperation[]; operations: IValidEditOperation[];
} }
interface IStackElement { interface IStackElement {
@@ -174,7 +174,7 @@ export class EditStack {
return stackElement!.afterCursorState; return stackElement!.afterCursorState;
} }
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null { private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
try { try {
return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null; return cursorStateComputer ? cursorStateComputer(inverseEditOperations) : null;
} catch (e) { } catch (e) {
@@ -6,7 +6,7 @@
import * as strings from 'vs/base/common/strings'; import * as strings from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot } from 'vs/editor/common/model'; import { ApplyEditsResult, EndOfLinePreference, FindMatch, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot, ValidAnnotatedEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { SearchData } from 'vs/editor/common/model/textModelSearch';
@@ -21,7 +21,7 @@ export interface IValidatedEditOperation {
isAutoWhitespaceEdit: boolean; isAutoWhitespaceEdit: boolean;
} }
export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation { export interface IReverseSingleEditOperation extends IValidEditOperation {
sortIndex: number; sortIndex: number;
} }
@@ -201,7 +201,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
this._pieceTree.setEOL(newEOL); this._pieceTree.setEOL(newEOL);
} }
public applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { public applyEdits(rawOperations: ValidAnnotatedEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
let mightContainRTL = this._mightContainRTL; let mightContainRTL = this._mightContainRTL;
let mightContainNonBasicASCII = this._mightContainNonBasicASCII; let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
let canReduceOperations = true; let canReduceOperations = true;
+29 -12
View File
@@ -1154,18 +1154,40 @@ export class TextModel extends Disposable implements model.ITextModel {
} }
} }
private _validateEditOperation(rawOperation: model.IIdentifiedSingleEditOperation): model.ValidAnnotatedEditOperation {
if (rawOperation instanceof model.ValidAnnotatedEditOperation) {
return rawOperation;
}
return new model.ValidAnnotatedEditOperation(
rawOperation.identifier || null,
this.validateRange(rawOperation.range),
rawOperation.text,
rawOperation.forceMoveMarkers || false,
rawOperation.isAutoWhitespaceEdit || false,
rawOperation._isTracked || false
);
}
private _validateEditOperations(rawOperations: model.IIdentifiedSingleEditOperation[]): model.ValidAnnotatedEditOperation[] {
const result: model.ValidAnnotatedEditOperation[] = [];
for (let i = 0, len = rawOperations.length; i < len; i++) {
result[i] = this._validateEditOperation(rawOperations[i]);
}
return result;
}
public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
try { try {
this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); return this._pushEditOperations(beforeCursorState, this._validateEditOperations(editOperations), cursorStateComputer);
} finally { } finally {
this._eventEmitter.endDeferredEmit(); this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit();
} }
} }
private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null {
if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) {
// Go through each saved line number and insert a trim whitespace edit // Go through each saved line number and insert a trim whitespace edit
// if it is safe to do so (no conflicts with other edits). // if it is safe to do so (no conflicts with other edits).
@@ -1238,10 +1260,8 @@ export class TextModel extends Disposable implements model.ITextModel {
} }
if (allowTrimLine) { if (allowTrimLine) {
editOperations.push({ const trimRange = new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn);
range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), editOperations.push(new model.ValidAnnotatedEditOperation(null, trimRange, null, false, false, false));
text: null
});
} }
} }
@@ -1252,21 +1272,18 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
} }
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
try { try {
this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
return this._applyEdits(rawOperations); return this._applyEdits(this._validateEditOperations(rawOperations));
} finally { } finally {
this._eventEmitter.endDeferredEmit(); this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit();
} }
} }
private _applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IIdentifiedSingleEditOperation[] { private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
for (let i = 0, len = rawOperations.length; i < len; i++) {
rawOperations[i].range = this.validateRange(rawOperations[i].range);
}
const oldLineCount = this._buffer.getLineCount(); const oldLineCount = this._buffer.getLineCount();
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
+1 -1
View File
@@ -1324,7 +1324,7 @@ export interface WorkspaceEditMetadata {
needsConfirmation: boolean; needsConfirmation: boolean;
label: string; label: string;
description?: string; description?: string;
iconPath?: { id: string } | { light: URI, dark: URI }; iconPath?: { id: string } | URI | { light: URI, dark: URI };
} }
export interface WorkspaceFileEditOptions { export interface WorkspaceFileEditOptions {
@@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { EditOperation } from 'vs/editor/common/core/editOperation'; import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions, IValidEditOperation } from 'vs/editor/common/model';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes'; import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes';
@@ -305,7 +305,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
model.pushEditOperations( model.pushEditOperations(
[], [],
ModelServiceImpl._computeEdits(model, textBuffer), ModelServiceImpl._computeEdits(model, textBuffer),
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => [] (inverseEditOperations: IValidEditOperation[]) => []
); );
model.pushStackElement(); model.pushStackElement();
} }
@@ -9,7 +9,7 @@ import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import { ICommand, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
export class BlockCommentCommand implements ICommand { export class BlockCommentCommand implements ICommand {
@@ -205,7 +205,7 @@ export class LineCommentCommand implements ICommand {
for (let i = 0, len = ops.length; i < len; i++) { for (let i = 0, len = ops.length; i < len; i++) {
builder.addEditOperation(ops[i].range, ops[i].text); builder.addEditOperation(ops[i].range, ops[i].text);
if (ops[i].range.isEmpty() && ops[i].range.getStartPosition().equals(cursorPosition)) { if (Range.isEmpty(ops[i].range) && Range.getStartPosition(ops[i].range).equals(cursorPosition)) {
const lineContent = model.getLineContent(cursorPosition.lineNumber); const lineContent = model.getLineContent(cursorPosition.lineNumber);
if (lineContent.length + 1 === cursorPosition.column) { if (lineContent.length + 1 === cursorPosition.column) {
this._deltaColumn = (ops[i].text || '').length; this._deltaColumn = (ops[i].text || '').length;
+27 -1
View File
@@ -25,6 +25,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as resources from 'vs/base/common/resources';
import * as strings from 'vs/base/common/strings';
function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString { function getHoverMessage(link: Link, useMetaKey: boolean): MarkdownString {
const executeCmd = link.url && /^command:/i.test(link.url.toString()); const executeCmd = link.url && /^command:/i.test(link.url.toString());
@@ -291,7 +295,29 @@ class LinkDetector implements IEditorContribution {
const { link } = occurrence; const { link } = occurrence;
link.resolve(CancellationToken.None).then(uri => { link.resolve(CancellationToken.None).then(uri => {
// open the uri
// Support for relative file URIs of the shape file://./relativeFile.txt or file:///./relativeFile.txt
if (typeof uri === 'string' && this.editor.hasModel()) {
const modelUri = this.editor.getModel().uri;
if (modelUri.scheme === Schemas.file && strings.startsWith(uri, 'file:')) {
const parsedUri = URI.parse(uri);
if (parsedUri.scheme === Schemas.file) {
const fsPath = resources.originalFSPath(parsedUri);
let relativePath: string | null = null;
if (strings.startsWith(fsPath, '/./')) {
relativePath = `.${fsPath.substr(1)}`;
} else if (strings.startsWith(fsPath, '//./')) {
relativePath = `.${fsPath.substr(2)}`;
}
if (relativePath) {
uri = resources.joinPath(modelUri, relativePath);
}
}
}
}
return this.openerService.open(uri, { openToSide, fromUserGesture }); return this.openerService.open(uri, { openToSide, fromUserGesture });
}, err => { }, err => {
@@ -26,7 +26,7 @@ import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService
import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService';
import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService, IConfigurationModel, IConfigurationValue, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { Configuration, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { Configuration, ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent } from 'vs/platform/configuration/common/configurationModels';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -448,10 +448,6 @@ export class SimpleConfigurationService implements IConfigurationService {
this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel());
} }
private configuration(): Configuration {
return this._configuration;
}
getValue<T>(): T; getValue<T>(): T;
getValue<T>(section: string): T; getValue<T>(section: string): T;
getValue<T>(overrides: IConfigurationOverrides): T; getValue<T>(overrides: IConfigurationOverrides): T;
@@ -459,20 +455,43 @@ export class SimpleConfigurationService implements IConfigurationService {
getValue(arg1?: any, arg2?: any): any { getValue(arg1?: any, arg2?: any): any {
const section = typeof arg1 === 'string' ? arg1 : undefined; const section = typeof arg1 === 'string' ? arg1 : undefined;
const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {};
return this.configuration().getValue(section, overrides, undefined); return this._configuration.getValue(section, overrides, undefined);
} }
public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise<void> { public updateValues(values: [string, any][]): Promise<void> {
this.configuration().updateValue(key, value); const previous = { data: this._configuration.toData() };
let changedKeys: string[] = [];
for (const entry of values) {
const [key, value] = entry;
if (this.getValue(key) === value) {
continue;
}
this._configuration.updateValue(key, value);
changedKeys.push(key);
}
if (changedKeys.length > 0) {
const configurationChangeEvent = new ConfigurationChangeEvent({ keys: changedKeys, overrides: [] }, previous, this._configuration);
configurationChangeEvent.source = ConfigurationTarget.MEMORY;
configurationChangeEvent.sourceConfig = null;
this._onDidChangeConfiguration.fire(configurationChangeEvent);
}
return Promise.resolve(); return Promise.resolve();
} }
public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise<void> {
return this.updateValues([[key, value]]);
}
public inspect<C>(key: string, options: IConfigurationOverrides = {}): IConfigurationValue<C> { public inspect<C>(key: string, options: IConfigurationOverrides = {}): IConfigurationValue<C> {
return this.configuration().inspect<C>(key, options, undefined); return this._configuration.inspect<C>(key, options, undefined);
} }
public keys() { public keys() {
return this.configuration().keys(undefined); return this._configuration.keys(undefined);
} }
public reloadConfiguration(): Promise<void> { public reloadConfiguration(): Promise<void> {
@@ -622,14 +641,18 @@ export function applyConfigurationValues(configurationService: IConfigurationSer
if (!(configurationService instanceof SimpleConfigurationService)) { if (!(configurationService instanceof SimpleConfigurationService)) {
return; return;
} }
let toUpdate: [string, any][] = [];
Object.keys(source).forEach((key) => { Object.keys(source).forEach((key) => {
if (isEditorConfigurationKey(key)) { if (isEditorConfigurationKey(key)) {
configurationService.updateValue(`editor.${key}`, source[key]); toUpdate.push([`editor.${key}`, source[key]]);
} }
if (isDiffEditor && isDiffEditorConfigurationKey(key)) { if (isDiffEditor && isDiffEditorConfigurationKey(key)) {
configurationService.updateValue(`diffEditor.${key}`, source[key]); toUpdate.push([`diffEditor.${key}`, source[key]]);
} }
}); });
if (toUpdate.length > 0) {
configurationService.updateValues(toUpdate);
}
} }
export class SimpleBulkEditService implements IBulkEditService { export class SimpleBulkEditService implements IBulkEditService {
@@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService';
import { IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -114,6 +114,11 @@ export interface IGlobalEditorOptions {
* Defaults to true. * Defaults to true.
*/ */
wordBasedSuggestions?: boolean; wordBasedSuggestions?: boolean;
/**
* Controls whether the semanticHighlighting is shown for the languages that support it.
* Defaults to true.
*/
'semanticHighlighting.enabled'?: boolean;
/** /**
* Keep peek editors open even when double clicking their content or when hitting `Escape`. * Keep peek editors open even when double clicking their content or when hitting `Escape`.
* Defaults to false. * Defaults to false.
@@ -443,7 +448,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IEditorProgressService editorProgressService: IEditorProgressService, @IEditorProgressService editorProgressService: IEditorProgressService,
@optional(IClipboardService) clipboardService: IClipboardService | null, @IClipboardService clipboardService: IClipboardService,
) { ) {
applyConfigurationValues(configurationService, options, true); applyConfigurationValues(configurationService, options, true);
const themeDomRegistration = (<StandaloneThemeServiceImpl>themeService).registerEditorContainer(domElement); const themeDomRegistration = (<StandaloneThemeServiceImpl>themeService).registerEditorContainer(domElement);
@@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration';
import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
@@ -122,7 +123,7 @@ export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorC
services.get(IConfigurationService), services.get(IConfigurationService),
services.get(IContextMenuService), services.get(IContextMenuService),
services.get(IEditorProgressService), services.get(IEditorProgressService),
null services.get(IClipboardService)
); );
}); });
} }
@@ -48,6 +48,8 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib
import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions';
import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
export interface IEditorOverrideServices { export interface IEditorOverrideServices {
[index: string]: any; [index: string]: any;
@@ -204,6 +206,8 @@ export class DynamicStandaloneServices extends Disposable {
let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService)));
ensure(IClipboardService, () => new BrowserClipboardService());
ensure(IContextMenuService, () => { ensure(IContextMenuService, () => {
const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService); const contextMenuService = new ContextMenuService(telemetryService, notificationService, contextViewService, keybindingService, themeService);
contextMenuService.configure({ blockMouse: false }); // we do not want that in the standalone editor contextMenuService.configure({ blockMouse: false }); // we do not want that in the standalone editor
@@ -2880,6 +2880,33 @@ suite('Editor Controller - Cursor Configuration', () => {
model.dispose(); model.dispose();
}); });
test('issue #90973: Undo brings back model alternative version', () => {
let model = createTextModel(
[
''
].join('\n'),
{
insertSpaces: false,
}
);
withTestCodeEditor(null, { model: model }, (editor, cursor) => {
const beforeVersion = model.getVersionId();
const beforeAltVersion = model.getAlternativeVersionId();
cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard');
cursorCommand(cursor, H.Undo, {});
const afterVersion = model.getVersionId();
const afterAltVersion = model.getAlternativeVersionId();
assert.notEqual(beforeVersion, afterVersion);
assert.equal(beforeAltVersion, afterAltVersion);
});
model.dispose();
});
}); });
suite('Editor Controller - Indentation Rules', () => { suite('Editor Controller - Indentation Rules', () => {
+5 -5
View File
@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { Range } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection, ISelection } from 'vs/editor/common/core/selection';
import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
@@ -50,7 +50,7 @@ export function testCommand(
export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] { export function getEditOperation(model: ITextModel, command: ICommand): IIdentifiedSingleEditOperation[] {
let operations: IIdentifiedSingleEditOperation[] = []; let operations: IIdentifiedSingleEditOperation[] = [];
let editOperationBuilder: IEditOperationBuilder = { let editOperationBuilder: IEditOperationBuilder = {
addEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { addEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => {
operations.push({ operations.push({
range: range, range: range,
text: text, text: text,
@@ -58,7 +58,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif
}); });
}, },
addTrackedEditOperation: (range: Range, text: string, forceMoveMarkers: boolean = false) => { addTrackedEditOperation: (range: IRange, text: string, forceMoveMarkers: boolean = false) => {
operations.push({ operations.push({
range: range, range: range,
text: text, text: text,
@@ -67,7 +67,7 @@ export function getEditOperation(model: ITextModel, command: ICommand): IIdentif
}, },
trackSelection: (selection: Selection) => { trackSelection: (selection: ISelection) => {
return ''; return '';
} }
}; };
@@ -5,7 +5,7 @@
import { CharCode } from 'vs/base/common/charCode'; import { CharCode } from 'vs/base/common/charCode';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferBuilder } from 'vs/editor/common/model'; import { DefaultEndOfLine, ITextBuffer, ITextBufferBuilder, ValidAnnotatedEditOperation } from 'vs/editor/common/model';
export function getRandomInt(min: number, max: number): number { export function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -31,7 +31,7 @@ export function getRandomString(minLength: number, maxLength: number): string {
return r; return r;
} }
export function generateRandomEdits(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { export function generateRandomEdits(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] {
let lines: string[] = []; let lines: string[] = [];
for (const chunk of chunks) { for (const chunk of chunks) {
let newLines = chunk.split(/\r\n|\r|\n/); let newLines = chunk.split(/\r\n|\r|\n/);
@@ -43,7 +43,7 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif
} }
} }
let ops: IIdentifiedSingleEditOperation[] = []; let ops: ValidAnnotatedEditOperation[] = [];
for (let i = 0; i < editCnt; i++) { for (let i = 0; i < editCnt; i++) {
let line = getRandomInt(1, lines.length); let line = getRandomInt(1, lines.length);
@@ -54,17 +54,14 @@ export function generateRandomEdits(chunks: string[], editCnt: number): IIdentif
text = getRandomString(5, 10); text = getRandomString(5, 10);
} }
ops.push({ ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), text, false, false, false));
text: text,
range: new Range(line, startColumn, line, endColumn)
});
lines[line - 1] = lines[line - 1].substring(0, startColumn - 1) + text + lines[line - 1].substring(endColumn - 1); lines[line - 1] = lines[line - 1].substring(0, startColumn - 1) + text + lines[line - 1].substring(endColumn - 1);
} }
return ops; return ops;
} }
export function generateSequentialInserts(chunks: string[], editCnt: number): IIdentifiedSingleEditOperation[] { export function generateSequentialInserts(chunks: string[], editCnt: number): ValidAnnotatedEditOperation[] {
let lines: string[] = []; let lines: string[] = [];
for (const chunk of chunks) { for (const chunk of chunks) {
let newLines = chunk.split(/\r\n|\r|\n/); let newLines = chunk.split(/\r\n|\r|\n/);
@@ -76,7 +73,7 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II
} }
} }
let ops: IIdentifiedSingleEditOperation[] = []; let ops: ValidAnnotatedEditOperation[] = [];
for (let i = 0; i < editCnt; i++) { for (let i = 0; i < editCnt; i++) {
let line = lines.length; let line = lines.length;
@@ -90,16 +87,13 @@ export function generateSequentialInserts(chunks: string[], editCnt: number): II
lines[line - 1] += text; lines[line - 1] += text;
} }
ops.push({ ops.push(new ValidAnnotatedEditOperation(null, new Range(line, column, line, column), text, false, false, false));
text: text,
range: new Range(line, column, line, column)
});
} }
return ops; return ops;
} }
export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): IIdentifiedSingleEditOperation[] { export function generateRandomReplaces(chunks: string[], editCnt: number, searchStringLen: number, replaceStringLen: number): ValidAnnotatedEditOperation[] {
let lines: string[] = []; let lines: string[] = [];
for (const chunk of chunks) { for (const chunk of chunks) {
let newLines = chunk.split(/\r\n|\r|\n/); let newLines = chunk.split(/\r\n|\r|\n/);
@@ -111,7 +105,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search
} }
} }
let ops: IIdentifiedSingleEditOperation[] = []; let ops: ValidAnnotatedEditOperation[] = [];
let chunkSize = Math.max(1, Math.floor(lines.length / editCnt)); let chunkSize = Math.max(1, Math.floor(lines.length / editCnt));
let chunkCnt = Math.floor(lines.length / chunkSize); let chunkCnt = Math.floor(lines.length / chunkSize);
let replaceString = getRandomString(replaceStringLen, replaceStringLen); let replaceString = getRandomString(replaceStringLen, replaceStringLen);
@@ -125,10 +119,7 @@ export function generateRandomReplaces(chunks: string[], editCnt: number, search
let startColumn = getRandomInt(1, maxColumn); let startColumn = getRandomInt(1, maxColumn);
let endColumn = Math.min(maxColumn, startColumn + searchStringLen); let endColumn = Math.min(maxColumn, startColumn + searchStringLen);
ops.push({ ops.push(new ValidAnnotatedEditOperation(null, new Range(line, startColumn, line, endColumn), replaceString, false, false, false));
text: replaceString,
range: new Range(line, startColumn, line, endColumn)
});
previousChunksLength = endLine; previousChunksLength = endLine;
} }
@@ -166,4 +157,4 @@ export function generateRandomChunkWithLF(minLength: number, maxLength: number):
} }
} }
return r; return r;
} }
+36 -7
View File
@@ -638,10 +638,18 @@ declare namespace monaco {
* Return the end position (which will be after or equal to the start position) * Return the end position (which will be after or equal to the start position)
*/ */
getEndPosition(): Position; getEndPosition(): Position;
/**
* Return the end position (which will be after or equal to the start position)
*/
static getEndPosition(range: IRange): Position;
/** /**
* Return the start position (which will be before or equal to the end position) * Return the start position (which will be before or equal to the end position)
*/ */
getStartPosition(): Position; getStartPosition(): Position;
/**
* Return the start position (which will be before or equal to the end position)
*/
static getStartPosition(range: IRange): Position;
/** /**
* Transform to a user presentable string representation. * Transform to a user presentable string representation.
*/ */
@@ -1098,6 +1106,11 @@ declare namespace monaco.editor {
* Defaults to true. * Defaults to true.
*/ */
wordBasedSuggestions?: boolean; wordBasedSuggestions?: boolean;
/**
* Controls whether the semanticHighlighting is shown for the languages that support it.
* Defaults to true.
*/
'semanticHighlighting.enabled'?: boolean;
/** /**
* Keep peek editors open even when double clicking their content or when hitting `Escape`. * Keep peek editors open even when double clicking their content or when hitting `Escape`.
* Defaults to false. * Defaults to false.
@@ -1508,7 +1521,7 @@ declare namespace monaco.editor {
/** /**
* The range to replace. This can be empty to emulate a simple insert. * The range to replace. This can be empty to emulate a simple insert.
*/ */
range: Range; range: IRange;
/** /**
* The text to replace with. This can be null to emulate a simple delete. * The text to replace with. This can be null to emulate a simple delete.
*/ */
@@ -1520,6 +1533,22 @@ declare namespace monaco.editor {
forceMoveMarkers?: boolean; forceMoveMarkers?: boolean;
} }
export interface IValidEditOperation {
/**
* The range to replace. This can be empty to emulate a simple insert.
*/
range: Range;
/**
* The text to replace with. This can be null to emulate a simple delete.
*/
text: string | null;
/**
* This indicates that this operation has "insert" semantics.
* i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved.
*/
forceMoveMarkers: boolean;
}
/** /**
* A callback that can compute the cursor state after applying a series of edit operations. * A callback that can compute the cursor state after applying a series of edit operations.
*/ */
@@ -1527,7 +1556,7 @@ declare namespace monaco.editor {
/** /**
* A callback that can compute the resulting cursors state after some edit operations have been executed. * A callback that can compute the resulting cursors state after some edit operations have been executed.
*/ */
(inverseEditOperations: IIdentifiedSingleEditOperation[]): Selection[] | null; (inverseEditOperations: IValidEditOperation[]): Selection[] | null;
} }
export class TextModelResolvedOptions { export class TextModelResolvedOptions {
@@ -1867,7 +1896,7 @@ declare namespace monaco.editor {
* @param operations The edit operations. * @param operations The edit operations.
* @return The inverse edit operations, that, when applied, will bring the model back to the previous state. * @return The inverse edit operations, that, when applied, will bring the model back to the previous state.
*/ */
applyEdits(operations: IIdentifiedSingleEditOperation[]): IIdentifiedSingleEditOperation[]; applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
/** /**
* Change the end of line sequence without recording in the undo stack. * Change the end of line sequence without recording in the undo stack.
* This can have dire consequences on the undo stack! See @pushEOL for the preferred way. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way.
@@ -1919,14 +1948,14 @@ declare namespace monaco.editor {
* @param range The range to replace (delete). May be empty to represent a simple insert. * @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete. * @param text The text to replace with. May be null to represent a simple delete.
*/ */
addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; addEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
/** /**
* Add a new edit operation (a replace operation). * Add a new edit operation (a replace operation).
* The inverse edits will be accessible in `ICursorStateComputerData.getInverseEditOperations()` * The inverse edits will be accessible in `ICursorStateComputerData.getInverseEditOperations()`
* @param range The range to replace (delete). May be empty to represent a simple insert. * @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete. * @param text The text to replace with. May be null to represent a simple delete.
*/ */
addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void; addTrackedEditOperation(range: IRange, text: string | null, forceMoveMarkers?: boolean): void;
/** /**
* Track `selection` when applying edit operations. * Track `selection` when applying edit operations.
* A best effort will be made to not grow/expand the selection. * A best effort will be made to not grow/expand the selection.
@@ -1946,7 +1975,7 @@ declare namespace monaco.editor {
/** /**
* Get the inverse edit operations of the added edit operations. * Get the inverse edit operations of the added edit operations.
*/ */
getInverseEditOperations(): IIdentifiedSingleEditOperation[]; getInverseEditOperations(): IValidEditOperation[];
/** /**
* Get a previously tracked selection. * Get a previously tracked selection.
* @param id The unique identifier returned by `trackSelection`. * @param id The unique identifier returned by `trackSelection`.
@@ -6043,7 +6072,7 @@ declare namespace monaco.languages {
description?: string; description?: string;
iconPath?: { iconPath?: {
id: string; id: string;
} | { } | Uri | {
light: Uri; light: Uri;
dark: Uri; dark: Uri;
}; };
@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { URI } from 'vs/base/common/uri';
export class BrowserClipboardService implements IClipboardService {
_serviceBrand: undefined;
private _internalResourcesClipboard: URI[] | undefined;
async writeText(text: string, type?: string): Promise<void> {
if (type) {
return; // TODO@sbatten
}
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
} else {
const activeElement = <HTMLElement>document.activeElement;
const newTextarea = document.createElement('textarea');
newTextarea.className = 'clipboard-copy';
newTextarea.style.visibility = 'false';
newTextarea.style.height = '1px';
newTextarea.style.width = '1px';
newTextarea.setAttribute('aria-hidden', 'true');
newTextarea.style.position = 'absolute';
newTextarea.style.top = '-1000';
newTextarea.style.left = '-1000';
document.body.appendChild(newTextarea);
newTextarea.value = text;
newTextarea.focus();
newTextarea.select();
document.execCommand('copy');
activeElement.focus();
document.body.removeChild(newTextarea);
}
return;
}
async readText(type?: string): Promise<string> {
if (type) {
return ''; // TODO@sbatten
}
return navigator.clipboard.readText();
}
readTextSync(): string | undefined {
return undefined;
}
readFindText(): string {
// @ts-ignore
return undefined;
}
writeFindText(text: string): void { }
writeResources(resources: URI[]): void {
this._internalResourcesClipboard = resources;
}
readResources(): URI[] {
return this._internalResourcesClipboard || [];
}
hasResources(): boolean {
return this._internalResourcesClipboard !== undefined && this._internalResourcesClipboard.length > 0;
}
}
@@ -49,6 +49,7 @@ export interface IProductConfiguration {
readonly extensionTips?: { [id: string]: string; }; readonly extensionTips?: { [id: string]: string; };
readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; };
readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; };
readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip; };
readonly extensionKeywords?: { [extension: string]: readonly string[]; }; readonly extensionKeywords?: { [extension: string]: readonly string[]; };
readonly keymapExtensionTips?: readonly string[]; readonly keymapExtensionTips?: readonly string[];
@@ -118,6 +119,11 @@ export interface IExeBasedExtensionTip {
exeFriendlyName?: string; exeFriendlyName?: string;
} }
export interface IRemoteExtensionTip {
friendlyName: string;
extensionId: string;
}
export interface ISurveyData { export interface ISurveyData {
surveyId: string; surveyId: string;
surveyUrl: string; surveyUrl: string;
+4 -1
View File
@@ -12,6 +12,9 @@ export function getRemoteAuthority(uri: URI): string | undefined {
return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined; return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined;
} }
export function getRemoteName(authority: string): string;
export function getRemoteName(authority: undefined): undefined;
export function getRemoteName(authority: string | undefined): string | undefined;
export function getRemoteName(authority: string | undefined): string | undefined { export function getRemoteName(authority: string | undefined): string | undefined {
if (!authority) { if (!authority) {
return undefined; return undefined;
@@ -22,4 +25,4 @@ export function getRemoteName(authority: string | undefined): string | undefined
return authority; return authority;
} }
return authority.substr(0, pos); return authority.substr(0, pos);
} }
@@ -16,11 +16,30 @@ import { Emitter, Event } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ParseError, parse } from 'vs/base/common/json'; import { ParseError, parse } from 'vs/base/common/json';
import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls';
type SyncConflictsClassification = { type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
}; };
export interface IRemoteUserData {
ref: string;
syncData: ISyncData | null;
}
export interface ISyncData {
version: number;
content: string;
}
function isSyncData(thing: any): thing is ISyncData {
return thing
&& (thing.version && typeof thing.version === 'number')
&& (thing.content && typeof thing.content === 'string')
&& Object.keys(thing).length === 2;
}
export abstract class AbstractSynchroniser extends Disposable { export abstract class AbstractSynchroniser extends Disposable {
protected readonly syncFolder: URI; protected readonly syncFolder: URI;
@@ -58,11 +77,11 @@ export abstract class AbstractSynchroniser extends Disposable {
this._onDidChangStatus.fire(status); this._onDidChangStatus.fire(status);
if (status === SyncStatus.HasConflicts) { if (status === SyncStatus.HasConflicts) {
// Log to telemetry when there is a sync conflict // Log to telemetry when there is a sync conflict
this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this.source }); this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsDetected', { source: this.source });
} }
if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) {
// Log to telemetry when conflicts are resolved // Log to telemetry when conflicts are resolved
this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: this.source }); this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/conflictsResolved', { source: this.source });
} }
} }
} }
@@ -88,6 +107,13 @@ export abstract class AbstractSynchroniser extends Disposable {
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) {
// current version is not compatible with cloud version
this.telemetryService.publicLog2<{ source: string }, SyncSourceClassification>('sync/incompatible', { source: this.source });
throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source);
}
return this.doSync(remoteUserData, lastSyncUserData); return this.doSync(remoteUserData, lastSyncUserData);
} }
@@ -98,8 +124,8 @@ export abstract class AbstractSynchroniser extends Disposable {
async getRemoteContent(): Promise<string | null> { async getRemoteContent(): Promise<string | null> {
const lastSyncData = await this.getLastSyncUserData(); const lastSyncData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncData); const { syncData } = await this.getRemoteUserData(lastSyncData);
return remoteUserData.content; return syncData ? syncData.content : null;
} }
async resetLocal(): Promise<void> { async resetLocal(): Promise<void> {
@@ -108,25 +134,56 @@ export abstract class AbstractSynchroniser extends Disposable {
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
} }
protected async getLastSyncUserData<T extends IUserData>(): Promise<T | null> { protected async getLastSyncUserData<T extends IRemoteUserData>(): Promise<T | null> {
try { try {
const content = await this.fileService.readFile(this.lastSyncResource); const content = await this.fileService.readFile(this.lastSyncResource);
return JSON.parse(content.value.toString()); const parsed = JSON.parse(content.value.toString());
let syncData: ISyncData = JSON.parse(parsed.content);
// Migration from old content to sync data
if (!isSyncData(syncData)) {
syncData = { version: this.version, content: parsed.content };
}
return { ...parsed, ...{ syncData, content: undefined } };
} catch (error) { } catch (error) {
return null; if (!(error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND)) {
// log error always except when file does not exist
this.logService.error(error);
}
} }
return null;
} }
protected async updateLastSyncUserData<T extends IUserData>(lastSyncUserData: T): Promise<void> { protected async updateLastSyncUserData(lastSyncRemoteUserData: IRemoteUserData, additionalProps: IStringDictionary<any> = {}): Promise<void> {
const lastSyncUserData: IUserData = { ref: lastSyncRemoteUserData.ref, content: JSON.stringify(lastSyncRemoteUserData.syncData), ...additionalProps };
await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData)));
} }
protected async getRemoteUserData(lastSyncData: IUserData | null): Promise<IUserData> { protected async getRemoteUserData(lastSyncData: IRemoteUserData | null): Promise<IRemoteUserData> {
return this.userDataSyncStoreService.read(this.resourceKey, lastSyncData, this.source); const lastSyncUserData: IUserData | null = lastSyncData ? { ref: lastSyncData.ref, content: lastSyncData.syncData ? JSON.stringify(lastSyncData.syncData) : null } : null;
const { ref, content } = await this.userDataSyncStoreService.read(this.resourceKey, lastSyncUserData, this.source);
let syncData: ISyncData | null = null;
if (content !== null) {
try {
syncData = <ISyncData>JSON.parse(content);
// Migration from old content to sync data
if (!isSyncData(syncData)) {
syncData = { version: this.version, content };
}
} catch (e) {
this.logService.error(e);
}
}
return { ref, syncData };
} }
protected async updateRemoteUserData(content: string, ref: string | null): Promise<string> { protected async updateRemoteUserData(content: string, ref: string | null): Promise<IRemoteUserData> {
return this.userDataSyncStoreService.write(this.resourceKey, content, ref, this.source); const syncData: ISyncData = { version: this.version, content };
ref = await this.userDataSyncStoreService.write(this.resourceKey, JSON.stringify(syncData), ref, this.source);
return { ref, syncData };
} }
protected async backupLocal(content: VSBuffer): Promise<void> { protected async backupLocal(content: VSBuffer): Promise<void> {
@@ -145,13 +202,14 @@ export abstract class AbstractSynchroniser extends Disposable {
} }
abstract readonly resourceKey: ResourceKey; abstract readonly resourceKey: ResourceKey;
protected abstract doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void>; protected abstract readonly version: number;
protected abstract doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void>;
} }
export interface IFileSyncPreviewResult { export interface IFileSyncPreviewResult {
readonly fileContent: IFileContent | null; readonly fileContent: IFileContent | null;
readonly remoteUserData: IUserData; readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IUserData | null; readonly lastSyncUserData: IRemoteUserData | null;
readonly content: string | null; readonly content: string | null;
readonly hasLocalChanged: boolean; readonly hasLocalChanged: boolean;
readonly hasRemoteChanged: boolean; readonly hasRemoteChanged: boolean;
@@ -190,7 +248,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
if (preview) { if (preview) {
if (this.syncPreviewResultPromise) { if (this.syncPreviewResultPromise) {
const result = await this.syncPreviewResultPromise; const result = await this.syncPreviewResultPromise;
return result.remoteUserData ? result.remoteUserData.content : null; return result.remoteUserData && result.remoteUserData.syncData ? result.remoteUserData.syncData.content : null;
} }
} }
return super.getRemoteContent(); return super.getRemoteContent();
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. 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 { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isNonEmptyArray } from 'vs/base/common/arrays'; import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
interface ISyncPreviewResult { interface ISyncPreviewResult {
@@ -22,18 +22,19 @@ interface ISyncPreviewResult {
readonly removed: IExtensionIdentifier[]; readonly removed: IExtensionIdentifier[];
readonly updated: ISyncExtension[]; readonly updated: ISyncExtension[];
readonly remote: ISyncExtension[] | null; readonly remote: ISyncExtension[] | null;
readonly remoteUserData: IUserData; readonly remoteUserData: IRemoteUserData;
readonly skippedExtensions: ISyncExtension[]; readonly skippedExtensions: ISyncExtension[];
readonly lastSyncUserData: ILastSyncUserData | null; readonly lastSyncUserData: ILastSyncUserData | null;
} }
interface ILastSyncUserData extends IUserData { interface ILastSyncUserData extends IRemoteUserData {
skippedExtensions: ISyncExtension[] | undefined; skippedExtensions: ISyncExtension[] | undefined;
} }
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'extensions'; readonly resourceKey: ResourceKey = 'extensions';
protected readonly version: number = 1;
constructor( constructor(
@IEnvironmentService environmentService: IEnvironmentService, @IEnvironmentService environmentService: IEnvironmentService,
@@ -72,9 +73,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>(); const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.content !== null) { if (remoteUserData.syncData !== null) {
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.content); const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.syncData.content);
const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions()); const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions());
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData }); await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData });
} }
@@ -145,7 +146,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return null; return null;
} }
protected async doSync(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<void> {
try { try {
const previewResult = await this.getPreview(remoteUserData, lastSyncUserData); const previewResult = await this.getPreview(remoteUserData, lastSyncUserData);
await this.apply(previewResult); await this.apply(previewResult);
@@ -163,9 +164,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
this.setStatus(SyncStatus.Idle); this.setStatus(SyncStatus.Idle);
} }
private async getPreview(remoteUserData: IUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> { private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
const remoteExtensions: ISyncExtension[] = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; const remoteExtensions: ISyncExtension[] = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.content!) : null; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.syncData!.content) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
@@ -201,15 +202,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// update remote // update remote
this.logService.trace('Extensions: Updating remote extensions...'); this.logService.trace('Extensions: Updating remote extensions...');
const content = JSON.stringify(remote); const content = JSON.stringify(remote);
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
remoteUserData = { ref, content };
this.logService.info('Extensions: Updated remote extensions'); this.logService.info('Extensions: Updated remote extensions');
} }
if (lastSyncUserData?.ref !== remoteUserData.ref) { if (lastSyncUserData?.ref !== remoteUserData.ref) {
// update last sync // update last sync
this.logService.trace('Extensions: Updating last synchronized extensions...'); this.logService.trace('Extensions: Updating last synchronized extensions...');
await this.updateLastSyncUserData<ILastSyncUserData>({ ...remoteUserData, skippedExtensions }); await this.updateLastSyncUserData(remoteUserData, { skippedExtensions });
this.logService.info('Extensions: Updated last synchronized extensions'); this.logService.info('Extensions: Updated last synchronized extensions');
} }
} }
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. 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 { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -13,7 +13,7 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { edit } from 'vs/platform/userDataSync/common/content'; import { edit } from 'vs/platform/userDataSync/common/content';
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
import { parse } from 'vs/base/common/json'; import { parse } from 'vs/base/common/json';
import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
const argvProperties: string[] = ['locale']; const argvProperties: string[] = ['locale'];
@@ -21,13 +21,14 @@ const argvProperties: string[] = ['locale'];
interface ISyncPreviewResult { interface ISyncPreviewResult {
readonly local: IGlobalState | undefined; readonly local: IGlobalState | undefined;
readonly remote: IGlobalState | undefined; readonly remote: IGlobalState | undefined;
readonly remoteUserData: IUserData; readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IUserData | null; readonly lastSyncUserData: IRemoteUserData | null;
} }
export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'globalState'; readonly resourceKey: ResourceKey = 'globalState';
protected readonly version: number = 1;
constructor( constructor(
@IFileService fileService: IFileService, @IFileService fileService: IFileService,
@@ -57,8 +58,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.content !== null) { if (remoteUserData.syncData !== null) {
const local: IGlobalState = JSON.parse(remoteUserData.content); const local: IGlobalState = JSON.parse(remoteUserData.syncData.content);
await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData }); await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData });
} }
@@ -119,7 +120,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
return null; return null;
} }
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void> { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void> {
try { try {
const result = await this.getPreview(remoteUserData, lastSyncUserData); const result = await this.getPreview(remoteUserData, lastSyncUserData);
await this.apply(result); await this.apply(result);
@@ -137,9 +138,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
} }
} }
private async getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, ): Promise<ISyncPreviewResult> { private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, ): Promise<ISyncPreviewResult> {
const remoteGlobalState: IGlobalState = remoteUserData.content ? JSON.parse(remoteUserData.content) : null; const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.content ? JSON.parse(lastSyncUserData.content) : null; const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
const localGloablState = await this.getLocalGlobalState(); const localGloablState = await this.getLocalGlobalState();
@@ -173,9 +174,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
// update remote // update remote
this.logService.trace('UI State: Updating remote ui state...'); this.logService.trace('UI State: Updating remote ui state...');
const content = JSON.stringify(remote); const content = JSON.stringify(remote);
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
this.logService.info('UI State: Updated remote ui state'); this.logService.info('UI State: Updated remote ui state');
remoteUserData = { ref, content };
} }
if (lastSyncUserData?.ref !== remoteUserData.ref) { if (lastSyncUserData?.ref !== remoteUserData.ref) {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json'; import { parse } from 'vs/base/common/json';
@@ -16,7 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { OS, OperatingSystem } from 'vs/base/common/platform'; import { OS, OperatingSystem } from 'vs/base/common/platform';
import { isUndefined } from 'vs/base/common/types'; import { isUndefined } from 'vs/base/common/types';
import { isNonEmptyArray } from 'vs/base/common/arrays'; import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
@@ -31,6 +31,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
readonly resourceKey: ResourceKey = 'keybindings'; readonly resourceKey: ResourceKey = 'keybindings';
protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; } protected get conflictsPreviewResource(): URI { return this.environmentService.keybindingsSyncPreviewResource; }
protected readonly version: number = 1;
constructor( constructor(
@IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService,
@@ -59,7 +60,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
const content = remoteUserData.content !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; const content = remoteUserData.syncData !== null ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
if (content !== null) { if (content !== null) {
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
@@ -160,7 +161,7 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null; return content !== null ? this.getKeybindingsContentFromSyncContent(content) : null;
} }
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<void> { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<void> {
try { try {
const result = await this.getPreview(remoteUserData, lastSyncUserData); const result = await this.getPreview(remoteUserData, lastSyncUserData);
if (result.hasConflicts) { if (result.hasConflicts) {
@@ -213,9 +214,8 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (hasRemoteChanged) { if (hasRemoteChanged) {
this.logService.trace('Keybindings: Updating remote keybindings...'); this.logService.trace('Keybindings: Updating remote keybindings...');
const remoteContents = this.updateSyncContent(content, remoteUserData.content); const remoteContents = this.updateSyncContent(content, remoteUserData.syncData ? remoteUserData.syncData.content : null);
const ref = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); remoteUserData = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref);
remoteUserData = { ref, content: remoteContents };
this.logService.info('Keybindings: Updated remote keybindings'); this.logService.info('Keybindings: Updated remote keybindings');
} }
@@ -230,23 +230,23 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem
if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) { if (lastSyncUserData?.ref !== remoteUserData.ref && (content !== null || fileContent !== null)) {
this.logService.trace('Keybindings: Updating last synchronized keybindings...'); this.logService.trace('Keybindings: Updating last synchronized keybindings...');
const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null); const lastSyncContent = this.updateSyncContent(content !== null ? content : fileContent!.value.toString(), null);
await this.updateLastSyncUserData({ ref: remoteUserData.ref, content: lastSyncContent }); await this.updateLastSyncUserData({ ref: remoteUserData.ref, syncData: { version: remoteUserData.syncData!.version, content: lastSyncContent } });
this.logService.info('Keybindings: Updated last synchronized keybindings'); this.logService.info('Keybindings: Updated last synchronized keybindings');
} }
this.syncPreviewResultPromise = null; this.syncPreviewResultPromise = null;
} }
private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null): Promise<IFileSyncPreviewResult> { private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<IFileSyncPreviewResult> {
if (!this.syncPreviewResultPromise) { if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token)); this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, token));
} }
return this.syncPreviewResultPromise; return this.syncPreviewResultPromise;
} }
private async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, token: CancellationToken): Promise<IFileSyncPreviewResult> { private async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, token: CancellationToken): Promise<IFileSyncPreviewResult> {
const remoteContent = remoteUserData.content ? this.getKeybindingsContentFromSyncContent(remoteUserData.content) : null; const remoteContent = remoteUserData.syncData ? this.getKeybindingsContentFromSyncContent(remoteUserData.syncData.content) : null;
const lastSyncContent = lastSyncUserData && lastSyncUserData.content ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.content) : null; const lastSyncContent = lastSyncUserData && lastSyncUserData.syncData ? this.getKeybindingsContentFromSyncContent(lastSyncUserData.syncData.content) : null;
// Get file content last to get the latest // Get file content last to get the latest
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
const formattingOptions = await this.getFormattingOptions(); const formattingOptions = await this.getFormattingOptions();
@@ -576,15 +576,17 @@ function parseSettings(content: string): INode[] {
if (hierarchyLevel === 0) { if (hierarchyLevel === 0) {
if (sep === ',') { if (sep === ',') {
const node = nodes.pop(); const node = nodes.pop();
nodes.push({ if (node) {
startOffset: node!.startOffset, nodes.push({
endOffset: node!.endOffset, startOffset: node.startOffset,
value: node!.value, endOffset: node.endOffset,
setting: { value: node.value,
key: node!.setting!.key, setting: {
hasCommaSeparator: true key: node.setting!.key,
} hasCommaSeparator: true
}); }
});
}
} }
} }
}, },
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IFileService, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, IUserData, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { parse } from 'vs/base/common/json'; import { parse } from 'vs/base/common/json';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
@@ -18,15 +18,26 @@ import * as arrays from 'vs/base/common/arrays';
import * as objects from 'vs/base/common/objects'; import * as objects from 'vs/base/common/objects';
import { isEmptyObject } from 'vs/base/common/types'; import { isEmptyObject } from 'vs/base/common/types';
import { edit } from 'vs/platform/userDataSync/common/content'; import { edit } from 'vs/platform/userDataSync/common/content';
import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
interface ISettingsSyncContent {
settings: string;
}
function isSettingsSyncContent(thing: any): thing is ISettingsSyncContent {
return thing
&& (thing.settings && typeof thing.settings === 'string')
&& Object.keys(thing).length === 1;
}
export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService { export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implements ISettingsSyncService {
_serviceBrand: any; _serviceBrand: any;
readonly resourceKey: ResourceKey = 'settings'; readonly resourceKey: ResourceKey = 'settings';
protected readonly version: number = 1;
protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; } protected get conflictsPreviewResource(): URI { return this.environmentService.settingsSyncPreviewResource; }
private _conflicts: IConflictSetting[] = []; private _conflicts: IConflictSetting[] = [];
@@ -77,12 +88,13 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
if (remoteUserData.content !== null) { if (remoteSettingsSyncContent !== null) {
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
const formatUtils = await this.getFormattingOptions(); const formatUtils = await this.getFormattingOptions();
// Update ignored settings from local file content // Update ignored settings from local file content
const content = updateIgnoredSettings(remoteUserData.content, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils);
this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({ this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve<IFileSyncPreviewResult>({
fileContent, fileContent,
remoteUserData, remoteUserData,
@@ -173,6 +185,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
async getRemoteContent(preview?: boolean): Promise<string | null> { async getRemoteContent(preview?: boolean): Promise<string | null> {
let content = await super.getRemoteContent(preview); let content = await super.getRemoteContent(preview);
if (content !== null) {
const settingsSyncContent = this.parseSettingsSyncContent(content);
content = settingsSyncContent ? settingsSyncContent.settings : null;
}
if (preview && content !== null) { if (preview && content !== null) {
const formatUtils = await this.getFormattingOptions(); const formatUtils = await this.getFormattingOptions();
// remove ignored settings from the remote content for preview // remove ignored settings from the remote content for preview
@@ -202,7 +218,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
} }
} }
protected async doSync(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise<void> { protected async doSync(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any | undefined }[] = []): Promise<void> {
try { try {
const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts); const result = await this.getPreview(remoteUserData, lastSyncUserData, resolvedConflicts);
if (result.hasConflicts) { if (result.hasConflicts) {
@@ -256,11 +272,11 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (hasRemoteChanged) { if (hasRemoteChanged) {
const formatUtils = await this.getFormattingOptions(); const formatUtils = await this.getFormattingOptions();
// Update ignored settings from remote // Update ignored settings from remote
content = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils); const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', getIgnoredSettings(this.configurationService, content), formatUtils);
this.logService.trace('Settings: Updating remote settings...'); this.logService.trace('Settings: Updating remote settings...');
const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); remoteUserData = await this.updateRemoteUserData(JSON.stringify(<ISettingsSyncContent>{ settings: content }), forcePush ? null : remoteUserData.ref);
this.logService.info('Settings: Updated remote settings'); this.logService.info('Settings: Updated remote settings');
remoteUserData = { ref, content };
} }
// Delete the preview // Delete the preview
@@ -280,16 +296,18 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
this.syncPreviewResultPromise = null; this.syncPreviewResultPromise = null;
} }
private getPreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> { private getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[]): Promise<IFileSyncPreviewResult> {
if (!this.syncPreviewResultPromise) { if (!this.syncPreviewResultPromise) {
this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token)); this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(remoteUserData, lastSyncUserData, resolvedConflicts, token));
} }
return this.syncPreviewResultPromise; return this.syncPreviewResultPromise;
} }
protected async generatePreview(remoteUserData: IUserData, lastSyncUserData: IUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> { protected async generatePreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null, resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<IFileSyncPreviewResult> {
const fileContent = await this.getLocalFileContent(); const fileContent = await this.getLocalFileContent();
const formattingOptions = await this.getFormattingOptions(); const formattingOptions = await this.getFormattingOptions();
const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData);
const lastSettingsSyncContent = lastSyncUserData ? this.getSettingsSyncContent(lastSyncUserData) : null;
let content: string | null = null; let content: string | null = null;
let hasLocalChanged: boolean = false; let hasLocalChanged: boolean = false;
@@ -297,7 +315,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
let hasConflicts: boolean = false; let hasConflicts: boolean = false;
let conflictSettings: IConflictSetting[] = []; let conflictSettings: IConflictSetting[] = [];
if (remoteUserData.content) { if (remoteSettingsSyncContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}'; const localContent: string = fileContent ? fileContent.value.toString() : '{}';
// No action when there are errors // No action when there are errors
@@ -307,7 +325,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
else { else {
this.logService.trace('Settings: Merging remote settings with local settings...'); this.logService.trace('Settings: Merging remote settings with local settings...');
const result = merge(localContent, remoteUserData.content, lastSyncUserData ? lastSyncUserData.content : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
content = result.localContent || result.remoteContent; content = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null; hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null; hasRemoteChanged = result.remoteContent !== null;
@@ -333,4 +351,17 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts }; return { fileContent, remoteUserData, lastSyncUserData, content, hasLocalChanged, hasRemoteChanged, hasConflicts };
} }
private getSettingsSyncContent(remoteUserData: IRemoteUserData): ISettingsSyncContent | null {
return remoteUserData.syncData ? this.parseSettingsSyncContent(remoteUserData.syncData.content) : null;
}
private parseSettingsSyncContent(syncContent: string): ISettingsSyncContent | null {
try {
const parsed = <ISettingsSyncContent>JSON.parse(syncContent);
return isSettingsSyncContent(parsed) ? parsed : /* migrate */ { settings: syncContent };
} catch (e) {
this.logService.error(e);
}
return null;
}
} }
@@ -5,7 +5,7 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionIdentifier, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry';
@@ -83,7 +83,12 @@ export function registerConfiguration(): IDisposable {
}, },
'sync.ignoredExtensions': { 'sync.ignoredExtensions': {
'type': 'array', 'type': 'array',
description: localize('sync.ignoredExtensions', "Configure extensions to be ignored while synchronizing."), 'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."),
items: {
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
},
'default': [], 'default': [],
'scope': ConfigurationScope.APPLICATION, 'scope': ConfigurationScope.APPLICATION,
uniqueItems: true uniqueItems: true
@@ -171,6 +176,7 @@ export enum UserDataSyncErrorCode {
// Local Errors // Local Errors
LocalPreconditionFailed = 'LocalPreconditionFailed', LocalPreconditionFailed = 'LocalPreconditionFailed',
LocalInvalidContent = 'LocalInvalidContent', LocalInvalidContent = 'LocalInvalidContent',
Incompatible = 'Incompatible',
Unknown = 'Unknown', Unknown = 'Unknown',
} }
@@ -335,6 +341,7 @@ export interface ISettingsSyncService extends IUserDataSynchroniser {
//#endregion //#endregion
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized); export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync'; export const USER_DATA_SYNC_SCHEME = 'vscode-userdata-sync';
export function toRemoteContentResource(source: SyncSource): URI { export function toRemoteContentResource(source: SyncSource): URI {
@@ -7,6 +7,11 @@ import { IUserDataSyncEnablementService, ResourceKey, ALL_RESOURCE_KEYS } from '
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { IStorageService, IWorkspaceStorageChangeEvent, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
type SyncEnablementClassification = {
enabled?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
const enablementKey = 'sync.enable'; const enablementKey = 'sync.enable';
function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; } function getEnablementKey(resourceKey: ResourceKey) { return `${enablementKey}.${resourceKey}`; }
@@ -22,7 +27,8 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event; readonly onDidChangeResourceEnablement: Event<[ResourceKey, boolean]> = this._onDidChangeResourceEnablement.event;
constructor( constructor(
@IStorageService private readonly storageService: IStorageService @IStorageService private readonly storageService: IStorageService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
) { ) {
super(); super();
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
@@ -34,6 +40,7 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
setEnablement(enabled: boolean): void { setEnablement(enabled: boolean): void {
if (this.isEnabled() !== enabled) { if (this.isEnabled() !== enabled) {
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(enablementKey, { enabled });
this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL); this.storageService.store(enablementKey, enabled, StorageScope.GLOBAL);
} }
} }
@@ -44,7 +51,9 @@ export class UserDataSyncEnablementService extends Disposable implements IUserDa
setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void { setResourceEnablement(resourceKey: ResourceKey, enabled: boolean): void {
if (this.isResourceEnabled(resourceKey) !== enabled) { if (this.isResourceEnabled(resourceKey) !== enabled) {
this.storageService.store(getEnablementKey(resourceKey), enabled, StorageScope.GLOBAL); const resourceEnablementKey = getEnablementKey(resourceKey);
this.telemetryService.publicLog2<{ enabled: boolean }, SyncEnablementClassification>(resourceEnablementKey, { enabled });
this.storageService.store(resourceEnablementKey, enabled, StorageScope.GLOBAL);
} }
} }
@@ -3,7 +3,7 @@
* Licensed under the Source EULA. 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 { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncService, SyncStatus, IUserDataSyncStoreService, SyncSource, ISettingsSyncService, IUserDataSyncLogService, IUserDataSynchroniser, UserDataSyncStoreError, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
@@ -49,7 +49,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
@IInstantiationService private readonly instantiationService: IInstantiationService, @IInstantiationService private readonly instantiationService: IInstantiationService,
@ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
@ITelemetryService private readonly telemetryService: ITelemetryService, @ITelemetryService private readonly telemetryService: ITelemetryService,
@IStorageService private readonly storageService: IStorageService, @IStorageService private readonly storageService: IStorageService,
) { ) {
@@ -62,7 +61,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (this.userDataSyncStoreService.userDataSyncStore) { if (this.userDataSyncStoreService.userDataSyncStore) {
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
this._register(this.userDataAuthTokenService.onDidChangeToken(e => this.onDidChangeAuthTokenStatus(e)));
} }
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
@@ -140,6 +138,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
} }
async stop(): Promise<void> { async stop(): Promise<void> {
await this.checkEnablement();
if (this.status === SyncStatus.Idle) { if (this.status === SyncStatus.Idle) {
return; return;
} }
@@ -201,7 +200,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
} }
private async hasPreviouslySynced(): Promise<boolean> { private async hasPreviouslySynced(): Promise<boolean> {
await this.checkEnablement();
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
if (await synchroniser.hasPreviouslySynced()) { if (await synchroniser.hasPreviouslySynced()) {
return true; return true;
@@ -211,7 +209,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
} }
private async hasLocalData(): Promise<boolean> { private async hasLocalData(): Promise<boolean> {
await this.checkEnablement();
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
if (await synchroniser.hasLocalData()) { if (await synchroniser.hasLocalData()) {
return true; return true;
@@ -288,14 +285,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
if (!this.userDataSyncStoreService.userDataSyncStore) { if (!this.userDataSyncStoreService.userDataSyncStore) {
throw new Error('Not enabled'); throw new Error('Not enabled');
} }
if (!(await this.userDataAuthTokenService.getToken())) {
throw new UserDataSyncError('Not Authenticated. Please sign in to start sync.', UserDataSyncErrorCode.Unauthorized);
}
} }
private onDidChangeAuthTokenStatus(token: string | undefined): void {
if (!token) {
this.stop();
}
}
} }
@@ -458,7 +458,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
let workspacesToRestore: IWorkspacePathToOpen[] = []; let workspacesToRestore: IWorkspacePathToOpen[] = [];
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) { if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
let foldersToRestore = this.backupMainService.getFolderBackupPaths(); let foldersToRestore = this.backupMainService.getFolderBackupPaths();
foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true }))); foldersToOpen.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f) })));
// collect from workspaces with hot-exit backups and from previous window session // collect from workspaces with hot-exit backups and from previous window session
workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()]; workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()];
+58
View File
@@ -2465,6 +2465,53 @@ declare module 'vscode' {
provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>; provideHover(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<Hover>;
} }
/**
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
* The result of this evaluation is shown in a tooltip-like widget.
* If only a range is specified, the expression will be extracted from the underlying document.
* An optional expression can be used to override the extracted expression.
* In this case the range is still used to highlight the range in the document.
*/
export class EvaluatableExpression {
/*
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
*/
readonly range: Range;
/*
* If specified the expression overrides the extracted expression.
*/
readonly expression?: string;
/**
* Creates a new evaluatable expression object.
*
* @param range The range in the underlying document from which the evaluatable expression is extracted.
* @param expression If specified overrides the extracted expression.
*/
constructor(range: Range, expression?: string);
}
/**
* The evaluatable expression provider interface defines the contract between extensions and
* the debug hover.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide an evaluatable expression for the given document and position.
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
*
* @param document The document in which the debug hover is opened.
* @param position The position in the document where the debug hover is opened.
* @param token A cancellation token.
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
/** /**
* A document highlight kind. * A document highlight kind.
*/ */
@@ -9080,6 +9127,17 @@ declare module 'vscode' {
*/ */
export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable; export function registerHoverProvider(selector: DocumentSelector, provider: HoverProvider): Disposable;
/**
* Register a provider that locates evaluatable expressions in text documents.
*
* If multiple providers are registered for a language an arbitrary provider will be used.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider An evaluatable expression provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
/** /**
* Register a document highlight provider. * Register a document highlight provider.
* *
+30 -61
View File
@@ -876,67 +876,7 @@ declare module 'vscode' {
//#endregion //#endregion
//#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084 //#region deprecated debug API
/**
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
* The result of this evaluation is shown in a tooltip-like widget.
* If only a range is specified, the expression will be extracted from the underlying document.
* An optional expression can be used to override the extracted expression.
* In this case the range is still used to highlight the range in the document.
*/
export class EvaluatableExpression {
/*
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
*/
readonly range: Range;
/*
* If specified the expression overrides the extracted expression.
*/
readonly expression?: string;
/**
* Creates a new evaluatable expression object.
*
* @param range The range in the underlying document from which the evaluatable expression is extracted.
* @param expression If specified overrides the extracted expression.
*/
constructor(range: Range, expression?: string);
}
/**
* The evaluatable expression provider interface defines the contract between extensions and
* the debug hover.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide an evaluatable expression for the given document and position.
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
*
* @param document The document in which the command was invoked.
* @param position The position where the command was invoked.
* @param token A cancellation token.
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export namespace languages {
/**
* Register a provider that locates evaluatable expressions in text documents.
*
* If multiple providers are registered for a language an arbitrary provider will be used.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider An evaluatable expression provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
}
// deprecated
export interface DebugConfigurationProvider { export interface DebugConfigurationProvider {
/** /**
@@ -1734,4 +1674,33 @@ declare module 'vscode' {
} }
//#endregion //#endregion
//#region Dialog title: https://github.com/microsoft/vscode/issues/82871
/**
* Options to configure the behaviour of a file open dialog.
*
* * Note 1: A dialog can select files, folders, or both. This is not true for Windows
* which enforces to open either files or folder, but *not both*.
* * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile
* and the editor then silently adjusts the options to select files.
*/
export interface OpenDialogOptions {
/**
* Dialog title
*/
title?: string;
}
/**
* Options to configure the behaviour of a file save dialog.
*/
export interface SaveDialogOptions {
/**
* Dialog title
*/
title?: string;
}
//#endregion
} }
@@ -37,7 +37,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders), canSelectFiles: options.canSelectFiles || (!options.canSelectFiles && !options.canSelectFolders),
canSelectFolders: options.canSelectFolders, canSelectFolders: options.canSelectFolders,
canSelectMany: options.canSelectMany, canSelectMany: options.canSelectMany,
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
title: options.title || undefined
}; };
if (options.filters) { if (options.filters) {
result.filters = []; result.filters = [];
@@ -49,7 +50,8 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions { private static _convertSaveOptions(options: MainThreadDialogSaveOptions): ISaveDialogOptions {
const result: ISaveDialogOptions = { const result: ISaveDialogOptions = {
defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined, defaultUri: options.defaultUri ? URI.revive(options.defaultUri) : undefined,
saveLabel: options.saveLabel || undefined saveLabel: options.saveLabel || undefined,
title: options.title || undefined
}; };
if (options.filters) { if (options.filters) {
result.filters = []; result.filters = [];
@@ -10,7 +10,7 @@ import { RenderLineNumbersType, TextEditorCursorStyle, cursorStyleToString, Edit
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon'; import { IDecorationOptions, ScrollType } from 'vs/editor/common/editorCommon';
import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel, ITextModelUpdateOptions } from 'vs/editor/common/model'; import { ISingleEditOperation, ITextModel, ITextModelUpdateOptions, IIdentifiedSingleEditOperation } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/modelService';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol'; import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
@@ -50,6 +50,7 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
// {{SQL CARBON EDIT}} // {{SQL CARBON EDIT}}
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
@@ -177,12 +178,14 @@ export interface MainThreadDialogOpenOptions {
canSelectFolders?: boolean; canSelectFolders?: boolean;
canSelectMany?: boolean; canSelectMany?: boolean;
filters?: { [name: string]: string[]; }; filters?: { [name: string]: string[]; };
title?: string;
} }
export interface MainThreadDialogSaveOptions { export interface MainThreadDialogSaveOptions {
defaultUri?: UriComponents; defaultUri?: UriComponents;
saveLabel?: string; saveLabel?: string;
filters?: { [name: string]: string[]; }; filters?: { [name: string]: string[]; };
title?: string;
} }
export interface MainThreadDiaglogsShape extends IDisposable { export interface MainThreadDiaglogsShape extends IDisposable {
@@ -1098,18 +1101,25 @@ export interface IWorkspaceSymbolsDto extends IdObject {
symbols: IWorkspaceSymbolDto[]; symbols: IWorkspaceSymbolDto[];
} }
export interface IWorkspaceEditEntryMetadataDto {
needsConfirmation: boolean;
label: string;
description?: string;
iconPath?: { id: string } | UriComponents | { light: UriComponents, dark: UriComponents };
}
export interface IWorkspaceFileEditDto { export interface IWorkspaceFileEditDto {
oldUri?: UriComponents; oldUri?: UriComponents;
newUri?: UriComponents; newUri?: UriComponents;
options?: modes.WorkspaceFileEditOptions options?: modes.WorkspaceFileEditOptions
metadata?: modes.WorkspaceEditMetadata; metadata?: IWorkspaceEditEntryMetadataDto;
} }
export interface IWorkspaceTextEditDto { export interface IWorkspaceTextEditDto {
resource: UriComponents; resource: UriComponents;
edit: modes.TextEdit; edit: modes.TextEdit;
modelVersionId?: number; modelVersionId?: number;
metadata?: modes.WorkspaceEditMetadata; metadata?: IWorkspaceEditEntryMetadataDto;
} }
export interface IWorkspaceEditDto { export interface IWorkspaceEditDto {
@@ -1128,6 +1138,9 @@ export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): mod
(<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri); (<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri);
(<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri); (<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri);
} }
if (edit.metadata && edit.metadata.iconPath) {
edit.metadata = revive(edit.metadata);
}
} }
} }
return <modes.WorkspaceEdit>data; return <modes.WorkspaceEdit>data;
@@ -736,6 +736,35 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
} }
}); });
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.toggleSelection',
weight: KeybindingWeight.WorkbenchContrib,
when: WorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter,
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
if (!widget || isLegacyTree(widget)) {
return;
}
const focus = widget.getFocus();
if (focus.length === 0) {
return;
}
const selection = widget.getSelection();
const index = selection.indexOf(focus[0]);
if (index > -1) {
widget.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]);
} else {
widget.setSelection([...selection, focus[0]]);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.toggleExpand', id: 'list.toggleExpand',
weight: KeybindingWeight.WorkbenchContrib, weight: KeybindingWeight.WorkbenchContrib,
+2 -7
View File
@@ -28,7 +28,6 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/commo
import { withNullAsUndefined } from 'vs/base/common/types'; import { withNullAsUndefined } from 'vs/base/common/types';
import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IHostService } from 'vs/workbench/services/host/browser/host';
import { isStandalone } from 'vs/base/browser/browser'; import { isStandalone } from 'vs/base/browser/browser';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
export interface IDraggedResource { export interface IDraggedResource {
@@ -343,7 +342,6 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
// Editors: enables cross window DND of tabs into the editor area // Editors: enables cross window DND of tabs into the editor area
const textFileService = accessor.get(ITextFileService); const textFileService = accessor.get(ITextFileService);
const editorService = accessor.get(IEditorService); const editorService = accessor.get(IEditorService);
const modelService = accessor.get(IModelService);
const draggedEditors: ISerializedDraggedEditor[] = []; const draggedEditors: ISerializedDraggedEditor[] = [];
files.forEach(file => { files.forEach(file => {
@@ -374,11 +372,8 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
// If the resource is dirty or untitled, send over its content // If the resource is dirty or untitled, send over its content
// to restore dirty state. Get that from the text model directly // to restore dirty state. Get that from the text model directly
let content: string | undefined = undefined; let content: string | undefined = undefined;
if (textFileService.isDirty(file.resource)) { if (model?.isDirty()) {
const textModel = modelService.getModel(file.resource); content = model.textEditorModel.getValue();
if (textModel) {
content = textModel.getValue();
}
} }
// Add as dragged editor // Add as dragged editor
+2 -2
View File
@@ -148,8 +148,8 @@ export class ResourceLabels extends Disposable {
})); }));
// notify when untitled labels change // notify when untitled labels change
this.textFileService.untitled.onDidChangeLabel(resource => { this.textFileService.untitled.onDidChangeLabel(model => {
this._widgets.forEach(widget => widget.notifyUntitledLabelChange(resource)); this._widgets.forEach(widget => widget.notifyUntitledLabelChange(model.resource));
}); });
} }
+11 -6
View File
@@ -122,8 +122,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private workbenchGrid!: SerializableGrid<ISerializableView>; private workbenchGrid!: SerializableGrid<ISerializableView>;
private editorWidgetSet = new Set<IEditor>();
private disposed: boolean | undefined; private disposed: boolean | undefined;
private titleBarPartView!: ISerializableView; private titleBarPartView!: ISerializableView;
@@ -198,7 +196,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
wasSideBarVisible: false, wasSideBarVisible: false,
wasPanelVisible: false, wasPanelVisible: false,
transitionDisposables: new DisposableStore(), transitionDisposables: new DisposableStore(),
setNotificationsFilter: false setNotificationsFilter: false,
editorWidgetSet: new Set<IEditor>()
}, },
}; };
@@ -708,15 +707,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
editor.updateOptions({ lineNumbers }); editor.updateOptions({ lineNumbers });
}; };
const editorWidgetSet = this.state.zenMode.editorWidgetSet;
if (!lineNumbers) { if (!lineNumbers) {
// Reset line numbers on all editors visible and non-visible // Reset line numbers on all editors visible and non-visible
for (const editor of this.editorWidgetSet) { for (const editor of editorWidgetSet) {
setEditorLineNumbers(editor); setEditorLineNumbers(editor);
} }
this.editorWidgetSet.clear(); editorWidgetSet.clear();
} else { } else {
this.editorService.visibleTextEditorWidgets.forEach(editor => { this.editorService.visibleTextEditorWidgets.forEach(editor => {
this.editorWidgetSet.add(editor); if (!editorWidgetSet.has(editor)) {
editorWidgetSet.add(editor);
this.state.zenMode.transitionDisposables.add(editor.onDidDispose(() => {
editorWidgetSet.delete(editor);
}));
}
setEditorLineNumbers(editor); setEditorLineNumbers(editor);
}); });
} }
@@ -470,7 +470,7 @@ export class BreadcrumbsControl {
this._ckBreadcrumbsActive.set(value); this._ckBreadcrumbsActive.set(value);
} }
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void { private _revealInEditor(event: IBreadcrumbsItemEvent, element: BreadcrumbElement, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void {
if (element instanceof FileElement) { if (element instanceof FileElement) {
if (element.kind === FileKind.FILE) { if (element.kind === FileKind.FILE) {
// open file in any editor // open file in any editor
@@ -71,10 +71,8 @@ export class BaseSplitEditorAction extends Action {
})); }));
} }
run(context?: IEditorIdentifier): Promise<any> { async run(context?: IEditorIdentifier): Promise<any> {
splitEditor(this.editorGroupService, this.direction, context); splitEditor(this.editorGroupService, this.direction, context);
return Promise.resolve(true);
} }
} }
@@ -183,7 +181,7 @@ export class JoinTwoGroupsAction extends Action {
super(id, label); super(id, label);
} }
run(context?: IEditorIdentifier): Promise<any> { async run(context?: IEditorIdentifier): Promise<any> {
let sourceGroup: IEditorGroup | undefined; let sourceGroup: IEditorGroup | undefined;
if (context && typeof context.groupId === 'number') { if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId); sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -198,12 +196,10 @@ export class JoinTwoGroupsAction extends Action {
if (targetGroup && sourceGroup !== targetGroup) { if (targetGroup && sourceGroup !== targetGroup) {
this.editorGroupService.mergeGroup(sourceGroup, targetGroup); this.editorGroupService.mergeGroup(sourceGroup, targetGroup);
return Promise.resolve(true); break;
} }
} }
} }
return Promise.resolve(true);
} }
} }
@@ -220,10 +216,8 @@ export class JoinAllGroupsAction extends Action {
super(id, label); super(id, label);
} }
run(context?: IEditorIdentifier): Promise<any> { async run(context?: IEditorIdentifier): Promise<any> {
mergeAllGroups(this.editorGroupService); mergeAllGroups(this.editorGroupService);
return Promise.resolve(true);
} }
} }
@@ -240,11 +234,9 @@ export class NavigateBetweenGroupsAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true); const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
nextGroup.focus(); nextGroup.focus();
return Promise.resolve(true);
} }
} }
@@ -261,10 +253,8 @@ export class FocusActiveGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.editorGroupService.activeGroup.focus(); this.editorGroupService.activeGroup.focus();
return Promise.resolve(true);
} }
} }
@@ -279,13 +269,11 @@ export abstract class BaseFocusGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true); const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
if (group) { if (group) {
group.focus(); group.focus();
} }
return Promise.resolve(true);
} }
} }
@@ -421,7 +409,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical'; this.class = (preferredDirection === GroupDirection.RIGHT) ? 'codicon-split-horizontal' : 'codicon-split-vertical';
} }
run(context: any): Promise<any> { async run(context: any): Promise<any> {
const entry = toEditorQuickOpenEntry(context); const entry = toEditorQuickOpenEntry(context);
if (entry) { if (entry) {
const input = entry.getInput(); const input = entry.getInput();
@@ -436,8 +424,6 @@ export class OpenToSideFromQuickOpenAction extends Action {
return this.editorService.openEditor(resourceInput, SIDE_GROUP); return this.editorService.openEditor(resourceInput, SIDE_GROUP);
} }
} }
return Promise.resolve(false);
} }
} }
@@ -490,7 +476,7 @@ export class CloseOneEditorAction extends Action {
super(id, label, 'codicon-close'); super(id, label, 'codicon-close');
} }
run(context?: IEditorCommandsContext): Promise<any> { async run(context?: IEditorCommandsContext): Promise<any> {
let group: IEditorGroup | undefined; let group: IEditorGroup | undefined;
let editorIndex: number | undefined; let editorIndex: number | undefined;
if (context) { if (context) {
@@ -517,8 +503,6 @@ export class CloseOneEditorAction extends Action {
if (group.activeEditor) { if (group.activeEditor) {
return group.closeEditor(group.activeEditor); return group.closeEditor(group.activeEditor);
} }
return Promise.resolve(false);
} }
} }
@@ -554,8 +538,6 @@ export class RevertAndCloseEditorAction extends Action {
group.closeEditor(editor); group.closeEditor(editor);
} }
return true;
} }
} }
@@ -573,13 +555,11 @@ export class CloseLeftEditorsInGroupAction extends Action {
super(id, label); super(id, label);
} }
run(context?: IEditorIdentifier): Promise<any> { async run(context?: IEditorIdentifier): Promise<any> {
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context); const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
if (group && editor) { if (group && editor) {
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor }); return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
} }
return Promise.resolve(false);
} }
} }
@@ -736,9 +716,9 @@ export class CloseEditorsInOtherGroupsAction extends Action {
run(context?: IEditorIdentifier): Promise<any> { run(context?: IEditorIdentifier): Promise<any> {
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => { return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(async g => {
if (groupToSkip && g.id === groupToSkip.id) { if (groupToSkip && g.id === groupToSkip.id) {
return Promise.resolve(); return;
} }
return g.closeAllEditors(); return g.closeAllEditors();
@@ -760,13 +740,11 @@ export class CloseEditorInAllGroupsAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const activeEditor = this.editorService.activeEditor; const activeEditor = this.editorService.activeEditor;
if (activeEditor) { if (activeEditor) {
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
} }
return Promise.resolve();
} }
} }
@@ -781,7 +759,7 @@ export class BaseMoveGroupAction extends Action {
super(id, label); super(id, label);
} }
run(context?: IEditorIdentifier): Promise<any> { async run(context?: IEditorIdentifier): Promise<any> {
let sourceGroup: IEditorGroup | undefined; let sourceGroup: IEditorGroup | undefined;
if (context && typeof context.groupId === 'number') { if (context && typeof context.groupId === 'number') {
sourceGroup = this.editorGroupService.getGroup(context.groupId); sourceGroup = this.editorGroupService.getGroup(context.groupId);
@@ -795,8 +773,6 @@ export class BaseMoveGroupAction extends Action {
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
} }
} }
return Promise.resolve(true);
} }
private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined { private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined {
@@ -892,10 +868,8 @@ export class MinimizeOtherGroupsAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
return Promise.resolve(false);
} }
} }
@@ -908,10 +882,8 @@ export class ResetGroupSizesAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN); this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
return Promise.resolve(false);
} }
} }
@@ -924,10 +896,8 @@ export class ToggleGroupSizesAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE); this.editorGroupService.arrangeGroups(GroupsArrangement.TOGGLE);
return Promise.resolve(false);
} }
} }
@@ -946,13 +916,11 @@ export class MaximizeGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
if (this.editorService.activeEditor) { if (this.editorService.activeEditor) {
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
this.layoutService.setSideBarHidden(true); this.layoutService.setSideBarHidden(true);
} }
return Promise.resolve(false);
} }
} }
@@ -967,23 +935,21 @@ export abstract class BaseNavigateEditorAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const result = this.navigate(); const result = this.navigate();
if (!result) { if (!result) {
return Promise.resolve(false); return;
} }
const { groupId, editor } = result; const { groupId, editor } = result;
if (!editor) { if (!editor) {
return Promise.resolve(false); return;
} }
const group = this.editorGroupService.getGroup(groupId); const group = this.editorGroupService.getGroup(groupId);
if (group) { if (group) {
return group.openEditor(editor); return group.openEditor(editor);
} }
return Promise.resolve();
} }
protected abstract navigate(): IEditorIdentifier | undefined; protected abstract navigate(): IEditorIdentifier | undefined;
@@ -1158,10 +1124,8 @@ export class NavigateForwardAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.forward(); this.historyService.forward();
return Promise.resolve();
} }
} }
@@ -1174,10 +1138,8 @@ export class NavigateBackwardsAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.back(); this.historyService.back();
return Promise.resolve();
} }
} }
@@ -1190,10 +1152,8 @@ export class NavigateToLastEditLocationAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.openLastEditLocation(); this.historyService.openLastEditLocation();
return Promise.resolve();
} }
} }
@@ -1206,10 +1166,8 @@ export class NavigateLastAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.last(); this.historyService.last();
return Promise.resolve();
} }
} }
@@ -1226,10 +1184,8 @@ export class ReopenClosedEditorAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.reopenLastClosedEditor(); this.historyService.reopenLastClosedEditor();
return Promise.resolve(false);
} }
} }
@@ -1247,15 +1203,13 @@ export class ClearRecentFilesAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
// Clear global recently opened // Clear global recently opened
this.workspacesService.clearRecentlyOpened(); this.workspacesService.clearRecentlyOpened();
// Clear workspace specific recently opened // Clear workspace specific recently opened
this.historyService.clearRecentlyOpened(); this.historyService.clearRecentlyOpened();
return Promise.resolve(false);
} }
} }
@@ -1313,12 +1267,10 @@ export class BaseQuickOpenEditorAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const keybindings = this.keybindingService.lookupKeybindings(this.id); const keybindings = this.keybindingService.lookupKeybindings(this.id);
this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } }); this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } });
return Promise.resolve(true);
} }
} }
@@ -1396,12 +1348,10 @@ export class QuickOpenPreviousEditorFromHistoryAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
const keybindings = this.keybindingService.lookupKeybindings(this.id); const keybindings = this.keybindingService.lookupKeybindings(this.id);
this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } }); this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings } });
return Promise.resolve(true);
} }
} }
@@ -1418,10 +1368,8 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.openNextRecentlyUsedEditor(); this.historyService.openNextRecentlyUsedEditor();
return Promise.resolve();
} }
} }
@@ -1438,10 +1386,8 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.openPreviouslyUsedEditor(); this.historyService.openPreviouslyUsedEditor();
return Promise.resolve();
} }
} }
@@ -1459,10 +1405,8 @@ export class OpenNextRecentlyUsedEditorInGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id); this.historyService.openNextRecentlyUsedEditor(this.editorGroupsService.activeGroup.id);
return Promise.resolve();
} }
} }
@@ -1480,10 +1424,8 @@ export class OpenPreviousRecentlyUsedEditorInGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id); this.historyService.openPreviouslyUsedEditor(this.editorGroupsService.activeGroup.id);
return Promise.resolve();
} }
} }
@@ -1500,12 +1442,10 @@ export class ClearEditorHistoryAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
// Editor history // Editor history
this.historyService.clear(); this.historyService.clear();
return Promise.resolve(true);
} }
} }
@@ -1772,10 +1712,8 @@ export class BaseCreateEditorGroupAction extends Action {
super(id, label); super(id, label);
} }
run(): Promise<any> { async run(): Promise<any> {
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true }); this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
return Promise.resolve(true);
} }
} }
@@ -317,8 +317,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution {
private registerListeners(): void { private registerListeners(): void {
this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar())); this._register(this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()));
this._register(this.textFileService.untitled.onDidChangeEncoding(r => this.onResourceEncodingChange(r))); this._register(this.textFileService.untitled.onDidChangeEncoding(model => this.onResourceEncodingChange(model.resource)));
this._register(this.textFileService.files.onDidChangeEncoding(m => this.onResourceEncodingChange((m.resource)))); this._register(this.textFileService.files.onDidChangeEncoding(model => this.onResourceEncodingChange((model.resource))));
this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange())); this._register(TabFocus.onDidChangeTabFocus(e => this.onTabFocusModeChange()));
} }
@@ -30,7 +30,6 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
/** /**
* The text editor that leverages the diff text editor for the editing experience. * The text editor that leverages the diff text editor for the editing experience.
@@ -51,8 +50,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
@IEditorService editorService: IEditorService, @IEditorService editorService: IEditorService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorGroupsService editorGroupService: IEditorGroupsService
@IClipboardService private clipboardService: IClipboardService
) { ) {
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService); super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
} }
@@ -75,10 +73,8 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
} }
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor { createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
if (this.reverseColor) { // {{SQL CARBON EDIT}} if (this.reverseColor) { (configuration as IDiffEditorOptions).reverse = true; } // {{SQL CARBON EDIT}}
(configuration as IDiffEditorOptions).reverse = true; return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
}
return this.instantiationService.createInstance(DiffEditorWidget as any, parent, configuration, this.clipboardService); // {{SQL CARBON EDIT}} strict-null-check...i guess?
} }
async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> { async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise<void> {
@@ -12,7 +12,7 @@ import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND,
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { firstIndex } from 'vs/base/common/arrays'; import { firstIndex } from 'vs/base/common/arrays';
import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { prepareActions } from 'vs/workbench/browser/actions'; import { prepareActions } from 'vs/workbench/browser/actions';
@@ -51,7 +51,6 @@ export interface IPaneColors extends IColorMapping {
} }
export interface IViewPaneOptions extends IPaneOptions { export interface IViewPaneOptions extends IPaneOptions {
actionRunner?: IActionRunner;
id: string; id: string;
title: string; title: string;
showActionsAlways?: boolean; showActionsAlways?: boolean;
@@ -169,7 +168,6 @@ export abstract class ViewPane extends Pane implements IView {
private readonly menuActions: ViewMenuActions; private readonly menuActions: ViewMenuActions;
protected actionRunner?: IActionRunner;
private toolbar?: ToolBar; private toolbar?: ToolBar;
private readonly showActionsAlways: boolean = false; private readonly showActionsAlways: boolean = false;
private headerContainer?: HTMLElement; private headerContainer?: HTMLElement;
@@ -196,7 +194,6 @@ export abstract class ViewPane extends Pane implements IView {
this.id = options.id; this.id = options.id;
this.title = options.title; this.title = options.title;
this.actionRunner = options.actionRunner;
this.showActionsAlways = !!options.showActionsAlways; this.showActionsAlways = !!options.showActionsAlways;
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
@@ -262,7 +259,6 @@ export abstract class ViewPane extends Pane implements IView {
actionViewItemProvider: action => this.getActionViewItem(action), actionViewItemProvider: action => this.getActionViewItem(action),
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
actionRunner: this.actionRunner
}); });
this._register(this.toolbar); this._register(this.toolbar);
@@ -311,7 +307,9 @@ export abstract class ViewPane extends Pane implements IView {
} }
focus(): void { focus(): void {
if (this.element) { if (this.shouldShowWelcome()) {
this.viewWelcomeContainer.focus();
} else if (this.element) {
this.element.focus(); this.element.focus();
this._onDidFocus.fire(); this._onDidFocus.fire();
} }
@@ -453,8 +451,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
private didLayout = false; private didLayout = false;
private dimension: Dimension | undefined; private dimension: Dimension | undefined;
protected actionRunner: IActionRunner | undefined;
private readonly visibleViewsCountFromCache: number | undefined; private readonly visibleViewsCountFromCache: number | undefined;
private readonly visibleViewsStorageId: string; private readonly visibleViewsStorageId: string;
protected readonly viewsModel: PersistentContributableViewsModel; protected readonly viewsModel: PersistentContributableViewsModel;
@@ -800,7 +796,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
{ {
id: viewDescriptor.id, id: viewDescriptor.id,
title: viewDescriptor.name, title: viewDescriptor.name,
actionRunner: this.getActionRunner(),
expanded: !collapsed, expanded: !collapsed,
minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120 minimumBodySize: this.viewDescriptorService.getViewContainerLocation(this.viewContainer) === ViewContainerLocation.Panel ? 0 : 120
}); });
@@ -831,14 +826,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
return panes; return panes;
} }
getActionRunner(): IActionRunner {
if (!this.actionRunner) {
this.actionRunner = new ActionRunner();
}
return this.actionRunner;
}
private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void { private onDidRemoveViewDescriptors(removed: IViewDescriptorRef[]): void {
removed = removed.sort((a, b) => b.index - a.index); removed = removed.sort((a, b) => b.index - a.index);
const panesToRemove: ViewPane[] = []; const panesToRemove: ViewPane[] = [];
+1 -1
View File
@@ -118,7 +118,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
try { try {
instantiationService.createInstance(ctor); instantiationService.createInstance(ctor);
} catch (error) { } catch (error) {
console.error(`Unable to instantiate workbench contribution ${(ctor as any).name}.`, error); console.error(`Unable to instantiate workbench contribution ${ctor.name}.`, error);
} }
} }
} }
+1 -1
View File
@@ -165,7 +165,7 @@ export interface IFileInputFactory {
createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
isFileInput(obj: any): obj is IFileEditorInput; isFileInput(obj: unknown): obj is IFileEditorInput;
} }
export interface IEditorInputFactoryRegistry { export interface IEditorInputFactoryRegistry {
@@ -46,8 +46,8 @@ export interface ISerializedEditorGroup {
preview?: number; preview?: number;
} }
export function isSerializedEditorGroup(obj?: any): obj is ISerializedEditorGroup { export function isSerializedEditorGroup(obj?: unknown): obj is ISerializedEditorGroup {
const group: ISerializedEditorGroup = obj; const group = obj as ISerializedEditorGroup;
return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru); return obj && typeof obj === 'object' && Array.isArray(group.editors) && Array.isArray(group.mru);
} }
@@ -25,6 +25,7 @@ import { basename } from 'vs/base/common/resources';
import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { WorkspaceFileEdit } from 'vs/editor/common/modes'; import { WorkspaceFileEdit } from 'vs/editor/common/modes';
import { compare } from 'vs/base/common/strings'; import { compare } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
// --- VIEW MODEL // --- VIEW MODEL
@@ -420,6 +421,12 @@ export class CategoryElementRenderer implements ITreeRenderer<CategoryElement, F
const className = ThemeIcon.asClassName(metadata.iconPath); const className = ThemeIcon.asClassName(metadata.iconPath);
template.icon.className = className ? `theme-icon ${className}` : ''; template.icon.className = className ? `theme-icon ${className}` : '';
} else if (URI.isUri(metadata.iconPath)) {
// background-image
template.icon.className = 'uri-icon';
template.icon.style.setProperty('--background-dark', `url("${metadata.iconPath.toString(true)}")`);
template.icon.style.setProperty('--background-light', `url("${metadata.iconPath.toString(true)}")`);
} else if (metadata.iconPath) { } else if (metadata.iconPath) {
// background-image // background-image
template.icon.className = 'uri-icon'; template.icon.className = 'uri-icon';
@@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { Lazy } from 'vs/base/common/lazy'; import { Lazy } from 'vs/base/common/lazy';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { basename, isEqual } from 'vs/base/common/resources'; import { basename, isEqual, extname } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
@@ -180,21 +180,62 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
} }
} }
const resourceExt = extname(resource);
const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({ const items = customEditors.allEditors.map((editorDescriptor): IQuickPickItem => ({
label: editorDescriptor.displayName, label: editorDescriptor.displayName,
id: editorDescriptor.id, id: editorDescriptor.id,
description: editorDescriptor.id === currentlyOpenedEditorType description: editorDescriptor.id === currentlyOpenedEditorType
? nls.localize('openWithCurrentlyActive', "Currently Active") ? nls.localize('openWithCurrentlyActive', "Currently Active")
: undefined : undefined,
buttons: resourceExt ? [{
iconClass: 'codicon-settings-gear',
tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
}] : undefined
})); }));
const pick = await this.quickInputService.pick(items, {
placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)), const picker = this.quickInputService.createQuickPick();
picker.items = items;
picker.placeholder = nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource));
const pick = await new Promise<string | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined);
picker.dispose();
});
picker.onDidTriggerItemButton(e => {
const pick = e.item.id;
resolve(pick); // open the view
picker.dispose();
// And persist the setting
if (pick) {
const newAssociation: CustomEditorAssociation = { viewType: pick, filenamePattern: '*' + resourceExt };
const currentAssociations = [...this.configurationService.getValue<CustomEditorsAssociations>(customEditorsAssociationsKey)] || [];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.filenamePattern === newAssociation.filenamePattern) {
currentAssociations.splice(i, 1, newAssociation);
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
return;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
this.configurationService.updateValue(customEditorsAssociationsKey, currentAssociations);
}
});
picker.show();
}); });
if (!pick || !pick.id) { if (!pick) {
return undefined; // {{SQL CARBON EDIT}} strict-null-check return undefined; // {{SQL CARBON EDIT}} strict-null-check
} }
return this.openWith(resource, pick.id, options, group);
return this.openWith(resource, pick, options, group);
} }
public openWith( public openWith(
@@ -312,7 +353,11 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations'; export const customEditorsAssociationsKey = 'workbench.experimental.editorAssociations';
export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[]; export type CustomEditorAssociation = CustomEditorSelector & {
readonly viewType: string;
};
export type CustomEditorsAssociations = readonly CustomEditorAssociation[];
export class CustomEditorContribution extends Disposable implements IWorkbenchContribution { export class CustomEditorContribution extends Disposable implements IWorkbenchContribution {
constructor( constructor(
@@ -915,14 +915,13 @@ export class DebugSession implements IDebugSession {
// Disconnects and clears state. Session can be initialized again for a new connection. // Disconnects and clears state. Session can be initialized again for a new connection.
private shutdown(): void { private shutdown(): void {
dispose(this.rawListeners); dispose(this.rawListeners);
if (this.raw) {
this.raw.disconnect();
this.raw.dispose();
this.raw = undefined;
}
this.fetchThreadsScheduler = undefined; this.fetchThreadsScheduler = undefined;
this.model.clearThreads(this.getId(), true); this.model.clearThreads(this.getId(), true);
if (this.raw) {
const raw = this.raw;
this.raw = undefined;
raw.disconnect();
raw.dispose();
}
this._onDidChangeState.fire(); this._onDidChangeState.fire();
} }
@@ -13,31 +13,6 @@
height: 100%; height: 100%;
} }
.debug-pane .debug-start-view {
padding: 0 20px 0 20px;
}
.debug-pane .debug-start-view .monaco-button,
.debug-pane .debug-start-view .section {
margin-top: 20px;
}
.debug-pane .debug-start-view .top-section {
margin-top: 10px;
}
.debug-pane .debug-start-view .monaco-button {
max-width: 260px;
margin-left: auto;
margin-right: auto;
display: block;
}
.debug-pane .debug-start-view .click {
cursor: pointer;
color: #007ACC;
}
.monaco-workbench .debug-action.notification:after { .monaco-workbench .debug-action.notification:after {
content: ''; content: '';
width: 6px; width: 6px;
@@ -3,64 +3,33 @@
* Licensed under the Source EULA. 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 dom from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { StartAction, ConfigureAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { equals } from 'vs/base/common/arrays';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewDescriptorService, IViewsRegistry, Extensions } from 'vs/workbench/common/views';
import { IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform';
import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IOpenerService } from 'vs/platform/opener/common/opener';
const $ = dom.$; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys';
import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { isMacintosh } from 'vs/base/common/platform';
interface DebugStartMetrics { const CONTEXT_DEBUGGER_INTERESTED = new RawContextKey<boolean>('debuggerInterested', false);
debuggers?: string[];
}
type DebugStartMetricsClassification = {
debuggers?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
function createClickElement(textContent: string, action: () => any): HTMLSpanElement {
const clickElement = $('span.click');
clickElement.textContent = textContent;
clickElement.onclick = action;
clickElement.tabIndex = 0;
clickElement.onkeyup = (e) => {
const keyboardEvent = new StandardKeyboardEvent(e);
if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) {
action();
}
};
return clickElement;
}
export class StartView extends ViewPane { export class StartView extends ViewPane {
static ID = 'workbench.debug.startView'; static ID = 'workbench.debug.startView';
static LABEL = localize('start', "Start"); static LABEL = localize('start', "Start");
private debugButton!: Button; private debuggerInterestedContext: IContextKey<boolean>;
private firstMessageContainer!: HTMLElement;
private secondMessageContainer!: HTMLElement;
private clickElement: HTMLElement | undefined;
private debuggerLabels: string[] | undefined = undefined;
constructor( constructor(
options: IViewletViewOptions, options: IViewletViewOptions,
@@ -69,125 +38,45 @@ export class StartView extends ViewPane {
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@ICommandService private readonly commandService: ICommandService,
@IDebugService private readonly debugService: IDebugService, @IDebugService private readonly debugService: IDebugService,
@IEditorService private readonly editorService: IEditorService, @IEditorService private readonly editorService: IEditorService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IFileDialogService private readonly dialogService: IFileDialogService,
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IOpenerService openerService: IOpenerService, @IOpenerService openerService: IOpenerService,
) { ) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
this._register(editorService.onDidActiveEditorChange(() => this.updateView()));
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED.bindTo(contextKeyService);
const setContextKey = () => {
const activeEditor = this.editorService.activeTextEditorWidget;
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
this.debuggerInterestedContext.set(debuggerLabels.length > 0);
};
this._register(editorService.onDidActiveEditorChange(setContextKey));
this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey));
} }
private updateView(): void { shouldShowWelcome(): boolean {
const activeEditor = this.editorService.activeTextEditorWidget; return true;
const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor);
if (!equals(this.debuggerLabels, debuggerLabels)) {
this.debuggerLabels = debuggerLabels;
const enabled = this.debuggerLabels.length > 0;
this.debugButton.enabled = enabled;
const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID);
let debugLabel = this.debuggerLabels.length !== 1 ? localize('debug', "Run and Debug") : localize('debugWith', "Run and Debug {0}", this.debuggerLabels[0]);
if (debugKeybinding) {
debugLabel += ` (${debugKeybinding.getLabel()})`;
}
this.debugButton.label = debugLabel;
const emptyWorkbench = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY;
this.firstMessageContainer.innerHTML = '';
this.secondMessageContainer.innerHTML = '';
const secondMessageElement = $('span');
this.secondMessageContainer.appendChild(secondMessageElement);
const setSecondMessage = () => {
secondMessageElement.textContent = localize('specifyHowToRun', "To customize Run and Debug");
this.clickElement = createClickElement(localize('configure', " create a launch.json file."), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.configure', { debuggers: this.debuggerLabels });
this.commandService.executeCommand(ConfigureAction.ID);
});
this.secondMessageContainer.appendChild(this.clickElement);
};
const setSecondMessageWithFolder = () => {
secondMessageElement.textContent = localize('noLaunchConfiguration', "To customize Run and Debug, ");
this.clickElement = createClickElement(localize('openFolder', " open a folder"), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFolder', { debuggers: this.debuggerLabels });
this.dialogService.pickFolderAndOpen({ forceNewWindow: false });
});
this.secondMessageContainer.appendChild(this.clickElement);
const moreText = $('span.moreText');
moreText.textContent = localize('andconfigure', " and create a launch.json file.");
this.secondMessageContainer.appendChild(moreText);
};
if (enabled && !emptyWorkbench) {
setSecondMessage();
}
if (enabled && emptyWorkbench) {
setSecondMessageWithFolder();
}
if (!enabled && !emptyWorkbench) {
const firstMessageElement = $('span');
this.firstMessageContainer.appendChild(firstMessageElement);
firstMessageElement.textContent = localize('simplyDebugAndRun', "Open a file which can be debugged or run.");
setSecondMessage();
}
if (!enabled && emptyWorkbench) {
this.clickElement = createClickElement(localize('openFile', "Open a file"), () => {
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.openFile');
this.dialogService.pickFileAndOpen({ forceNewWindow: false });
});
this.firstMessageContainer.appendChild(this.clickElement);
const firstMessageElement = $('span');
this.firstMessageContainer.appendChild(firstMessageElement);
firstMessageElement.textContent = localize('canBeDebuggedOrRun', " which can be debugged or run.");
setSecondMessageWithFolder();
}
}
}
protected renderBody(container: HTMLElement): void {
super.renderBody(container);
this.firstMessageContainer = $('.top-section');
container.appendChild(this.firstMessageContainer);
this.debugButton = new Button(container);
this._register(this.debugButton.onDidClick(() => {
this.commandService.executeCommand(StartAction.ID);
this.telemetryService.publicLog2<DebugStartMetrics, DebugStartMetricsClassification>('debugStart.runAndDebug', { debuggers: this.debuggerLabels });
}));
attachButtonStyler(this.debugButton, this.themeService);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-start-view');
this.secondMessageContainer = $('.section');
container.appendChild(this.secondMessageContainer);
this.updateView();
}
protected layoutBody(_: number, __: number): void {
// no-op
}
focus(): void {
if (this.debugButton.enabled) {
this.debugButton.focus();
} else if (this.clickElement) {
this.clickElement.focus();
}
} }
} }
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID),
when: CONTEXT_DEBUGGER_INTERESTED.toNegated()
});
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
content: localize('runAndDebugAction', "[Run and Debug](command:{0})", StartAction.ID)
});
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID),
when: WorkbenchStateContext.notEqualsTo('empty')
});
viewsRegistry.registerViewWelcomeContent(StartView.ID, {
content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID),
when: WorkbenchStateContext.isEqualTo('empty')
});
@@ -1773,6 +1773,16 @@ declare module DebugProtocol {
If missing the value 0 is assumed which results in the completion text being inserted. If missing the value 0 is assumed which results in the completion text being inserted.
*/ */
length?: number; length?: number;
/** Determines the start of the new selection after the text has been inserted (or replaced).
The start position must in the range 0 and length of the completion text.
If omitted the selection starts at the end of the completion text.
*/
selectionStart?: number;
/** Determines the length of the new selection after the text has been inserted (or replaced).
The selection can not extend beyond the bounds of the completion text.
If omitted the length is assumed to be 0.
*/
selectionLength?: number;
} }
/** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions'; import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService'; import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import { import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
@@ -48,6 +48,8 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
// Singletons // Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -443,6 +445,33 @@ registerAction2(class extends Action2 {
} }
}); });
registerAction2(class extends Action2 {
constructor() {
super({
id: TOGGLE_IGNORE_EXTENSION_ACTION_ID,
title: { value: localize('workbench.extensions.action.toggleIgnoreExtension', "Don't Sync This Extension"), original: `Don't Sync This Extension` },
menu: {
id: MenuId.ExtensionContext,
group: '2_configure',
when: CONTEXT_SYNC_ENABLEMENT
},
});
}
async run(accessor: ServicesAccessor, id: string) {
const configurationService = accessor.get(IConfigurationService);
const ignoredExtensions = [...configurationService.getValue<string[]>('sync.ignoredExtensions')];
const index = ignoredExtensions.findIndex(ignoredExtension => areSameExtensions({ id: ignoredExtension }, { id }));
if (index !== -1) {
ignoredExtensions.splice(index, 1);
} else {
ignoredExtensions.push(id);
}
return configurationService.updateValue('sync.ignoredExtensions', ignoredExtensions.length ? ignoredExtensions : undefined, ConfigurationTarget.USER);
}
});
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench); const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
class ExtensionsContributions implements IWorkbenchContribution { class ExtensionsContributions implements IWorkbenchContribution {
@@ -13,7 +13,7 @@ import * as json from 'vs/base/common/json';
import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { dispose, Disposable } from 'vs/base/common/lifecycle'; import { dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, TOGGLE_IGNORE_EXTENSION_ACTION_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsLabel, IGalleryExtension, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, INSTALL_ERROR_NOT_SUPPORTED } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionTipsService, IExtensionRecommendation, IExtensionsConfigContent, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -679,7 +679,7 @@ export class DropDownMenuActionViewItem extends ExtensionActionViewItem {
} }
} }
export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, extension: IExtension | undefined | null): ExtensionAction[][] { export function getContextMenuActions(menuService: IMenuService, contextKeyService: IContextKeyService, instantiationService: IInstantiationService, extension: IExtension | undefined | null): ExtensionAction[][] {
const scopedContextKeyService = contextKeyService.createScoped(); const scopedContextKeyService = contextKeyService.createScoped();
if (extension) { if (extension) {
scopedContextKeyService.createKey<boolean>('isBuiltinExtension', extension.type === ExtensionType.System); scopedContextKeyService.createKey<boolean>('isBuiltinExtension', extension.type === ExtensionType.System);
@@ -691,7 +691,7 @@ export function getContextMenuActions(menuService: IMenuService, contextKeyServi
const groups: ExtensionAction[][] = []; const groups: ExtensionAction[][] = [];
const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService); const menu = menuService.createMenu(MenuId.ExtensionContext, scopedContextKeyService);
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => new MenuItemExtensionAction(action)))); menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => instantiationService.createInstance(MenuItemExtensionAction, action))));
menu.dispose(); menu.dispose();
return groups; return groups;
@@ -745,7 +745,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(UninstallAction)]);
groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]);
getContextMenuActions(this.menuService, this.contextKeyService, this.extension).forEach(actions => groups.push(actions)); getContextMenuActions(this.menuService, this.contextKeyService, this.instantiationService, this.extension).forEach(actions => groups.push(actions));
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension));
@@ -773,11 +773,21 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
export class MenuItemExtensionAction extends ExtensionAction { export class MenuItemExtensionAction extends ExtensionAction {
constructor(private readonly action: IAction) { constructor(
private readonly action: IAction,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(action.id, action.label); super(action.id, action.label);
} }
update() { } update() {
if (!this.extension) {
return;
}
if (this.action.id === TOGGLE_IGNORE_EXTENSION_ACTION_ID) {
this.checked = this.configurationService.getValue<string[]>('sync.ignoredExtensions').some(id => areSameExtensions({ id }, this.extension!.identifier));
}
}
async run(): Promise<void> { async run(): Promise<void> {
if (this.extension) { if (this.extension) {
@@ -247,7 +247,7 @@ export class ExtensionsListView extends ViewPane {
getActions: () => actions.slice(0, actions.length - 1) getActions: () => actions.slice(0, actions.length - 1)
}); });
} else if (e.element) { } else if (e.element) {
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), e.element); const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element);
groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!)); groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = e.element!));
let actions: IAction[] = []; let actions: IAction[] = [];
for (const menuActions of groups) { for (const menuActions of groups) {
@@ -143,3 +143,5 @@ export class ExtensionContainers extends Disposable {
} }
} }
} }
export const TOGGLE_IGNORE_EXTENSION_ACTION_ID = 'workbench.extensions.action.toggleIgnoreExtension';
@@ -13,13 +13,15 @@ export class ExtensionsInput extends EditorInput {
static readonly ID = 'workbench.extensions.input2'; static readonly ID = 'workbench.extensions.input2';
get extension(): IExtension { return this._extension; } get extension(): IExtension { return this._extension; }
readonly resource = URI.from({ get resource() {
scheme: 'extension', return URI.from({
path: this.extension.identifier.id scheme: 'extension',
}); path: this.extension.identifier.id
});
}
constructor( constructor(
private _extension: IExtension, private readonly _extension: IExtension
) { ) {
super(); super();
} }
@@ -16,10 +16,6 @@ export class RuntimeExtensionsInput extends EditorInput {
path: 'default' path: 'default'
}); });
constructor() {
super();
}
getTypeId(): string { getTypeId(): string {
return RuntimeExtensionsInput.ID; return RuntimeExtensionsInput.ID;
} }
@@ -7,7 +7,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor';
import { ITextFileService, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
@@ -62,9 +62,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e)));
// Ensure dirty text file and untitled models are always opened as editors // Ensure dirty text file and untitled models are always opened as editors
this._register(this.textFileService.files.onDidChangeDirty(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); this._register(this.textFileService.files.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
this._register(this.textFileService.files.onDidSaveError(m => this.ensureDirtyFilesAreOpenedWorker.work(m.resource))); this._register(this.textFileService.files.onDidSaveError(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
this._register(this.textFileService.untitled.onDidChangeDirty(r => this.ensureDirtyFilesAreOpenedWorker.work(r))); this._register(this.textFileService.untitled.onDidChangeDirty(model => this.ensureDirtyFilesAreOpenedWorker.work(model.resource)));
// Out of workspace file watchers // Out of workspace file watchers
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
@@ -290,7 +290,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
} }
const model = this.textFileService.files.get(resource); const model = this.textFileService.files.get(resource);
if (model?.hasState(ModelState.PENDING_SAVE)) { if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) {
return false; // resource must not be pending to save return false; // resource must not be pending to save
} }
@@ -369,18 +369,14 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
} }
const model = this.textFileService.files.get(resource); const model = this.textFileService.files.get(resource);
if (!model) { if (!model || model.isDirty() || !model.isResolved()) {
return undefined;
}
if (model.isDirty()) {
return undefined; return undefined;
} }
return model; return model;
})), })),
model => model.resource.toString() model => model.resource.toString()
).forEach(model => model.load()); ).forEach(model => this.textFileService.files.resolve(model.resource, { reload: { async: true } }));
} }
} }
@@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath';
import { basename } from 'vs/base/common/resources'; import { basename } from 'vs/base/common/resources';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
@@ -135,7 +135,7 @@ export class TextFileEditor extends BaseTextEditor {
return this.openAsBinary(input, options); return this.openAsBinary(input, options);
} }
const textFileModel = <ITextFileEditorModel>resolvedModel; const textFileModel = resolvedModel;
// Editor // Editor
const textEditor = assertIsDefined(this.getControl()); const textEditor = assertIsDefined(this.getControl());
@@ -221,13 +221,11 @@ class DoNotShowResolveConflictLearnMoreAction extends Action {
super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again")); super('workbench.files.action.resolveConflictLearnMoreDoNotShowAgain', nls.localize('dontShowAgain', "Don't Show Again"));
} }
run(notification: IDisposable): Promise<any> { async run(notification: IDisposable): Promise<any> {
this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL); this.storageService.store(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, true, StorageScope.GLOBAL);
// Hide notification // Hide notification
notification.dispose(); notification.dispose();
return Promise.resolve();
} }
} }
@@ -262,8 +260,6 @@ class ResolveSaveConflictAction extends Action {
Event.once(handle.onDidClose)(() => dispose(actions.primary!)); Event.once(handle.onDidClose)(() => dispose(actions.primary!));
pendingResolveSaveConflictMessages.push(handle); pendingResolveSaveConflictMessages.push(handle);
} }
return Promise.resolve(true);
} }
} }
@@ -276,7 +272,7 @@ class SaveElevatedAction extends Action {
super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo...")); super('workbench.files.action.saveElevated', triedToMakeWriteable ? isWindows ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('overwriteElevatedSudo', "Overwrite as Sudo...") : isWindows ? nls.localize('saveElevated', "Retry as Admin...") : nls.localize('saveElevatedSudo', "Retry as Sudo..."));
} }
run(): Promise<any> { async run(): Promise<any> {
if (!this.model.isDisposed()) { if (!this.model.isDisposed()) {
this.model.save({ this.model.save({
writeElevated: true, writeElevated: true,
@@ -284,8 +280,6 @@ class SaveElevatedAction extends Action {
reason: SaveReason.EXPLICIT reason: SaveReason.EXPLICIT
}); });
} }
return Promise.resolve(true);
} }
} }
@@ -297,12 +291,10 @@ class OverwriteReadonlyAction extends Action {
super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite")); super('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"));
} }
run(): Promise<any> { async run(): Promise<any> {
if (!this.model.isDisposed()) { if (!this.model.isDisposed()) {
this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT }); this.model.save({ overwriteReadonly: true, reason: SaveReason.EXPLICIT });
} }
return Promise.resolve(true);
} }
} }
@@ -314,12 +306,10 @@ class SaveIgnoreModifiedSinceAction extends Action {
super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite")); super('workbench.files.action.saveIgnoreModifiedSince', nls.localize('overwrite', "Overwrite"));
} }
run(): Promise<any> { async run(): Promise<any> {
if (!this.model.isDisposed()) { if (!this.model.isDisposed()) {
this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT }); this.model.save({ ignoreModifiedSince: true, reason: SaveReason.EXPLICIT });
} }
return Promise.resolve(true);
} }
} }
@@ -331,10 +321,8 @@ class ConfigureSaveConflictAction extends Action {
super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure")); super('workbench.files.action.configureSaveConflict', nls.localize('configure', "Configure"));
} }
run(): Promise<any> { async run(): Promise<any> {
this.preferencesService.openSettings(undefined, 'files.saveConflictResolution'); this.preferencesService.openSettings(undefined, 'files.saveConflictResolution');
return Promise.resolve(true);
} }
} }
@@ -18,7 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -34,6 +34,9 @@ import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { WorkbenchStateContext, RemoteNameContext, IsWebContext } from 'vs/workbench/browser/contextkeys';
import { AddRootFolderAction, OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { isMacintosh } from 'vs/base/common/platform';
export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {
@@ -61,6 +64,23 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor
private registerViews(): void { private registerViews(): void {
const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry); const viewsRegistry = Registry.as<IViewsRegistry>(Extensions.ViewsRegistry);
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.\n[Add Folder](command:{0})", AddRootFolderAction.ID),
when: WorkbenchStateContext.isEqualTo('workspace')
});
const commandId = isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID;
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('remoteNoFolderHelp', "Connected to remote.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.notEqualsTo(''), IsWebContext.toNegated())
});
viewsRegistry.registerViewWelcomeContent(EmptyView.ID, {
content: localize('noFolderHelp', "You have not yet opened a folder.\n[Open Folder](command:{0})", commandId),
when: ContextKeyExpr.or(ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), RemoteNameContext.isEqualTo('')), ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('workspace'), IsWebContext))
});
const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER);
let viewDescriptorsToRegister: IViewDescriptor[] = []; let viewDescriptorsToRegister: IViewDescriptor[] = [];
@@ -166,7 +166,7 @@ export class GlobalNewUntitledFileAction extends Action {
} }
} }
async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> { async function deleteFiles(workingCopyFileService: IWorkingCopyFileService, dialogService: IDialogService, configurationService: IConfigurationService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise<void> {
let primaryButton: string; let primaryButton: string;
if (useTrash) { if (useTrash) {
primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
@@ -174,20 +174,24 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete"); primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
} }
const distinctElements = resources.distinctParents(elements, e => e.resource);
// Handle dirty // Handle dirty
const distinctElements = resources.distinctParents(elements, e => e.resource);
const dirtyWorkingCopies = new Set<IWorkingCopy>();
for (const distinctElement of distinctElements) {
for (const dirtyWorkingCopy of workingCopyFileService.getDirty(distinctElement.resource)) {
dirtyWorkingCopies.add(dirtyWorkingCopy);
}
}
let confirmed = true; let confirmed = true;
const dirtyWorkingCopies = workingCopyService.dirtyWorkingCopies.filter(workingCopy => distinctElements.some(e => resources.isEqualOrParent(workingCopy.resource, e.resource))); if (dirtyWorkingCopies.size) {
if (dirtyWorkingCopies.length) {
let message: string; let message: string;
if (distinctElements.length > 1) { if (distinctElements.length > 1) {
message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?"); message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
} else if (distinctElements[0].isDirectory) { } else if (distinctElements[0].isDirectory) {
if (dirtyWorkingCopies.length === 1) { if (dirtyWorkingCopies.size === 1) {
message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name); message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder {0} with unsaved changes in 1 file. Do you want to continue?", distinctElements[0].name);
} else { } else {
message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.length); message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder {0} with unsaved changes in {1} files. Do you want to continue?", distinctElements[0].name, dirtyWorkingCopies.size);
} }
} else { } else {
message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name); message = nls.localize('dirtyMessageFileDelete', "You are deleting {0} with unsaved changes. Do you want to continue?", distinctElements[0].name);
@@ -204,7 +208,6 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
confirmed = false; confirmed = false;
} else { } else {
skipConfirm = true; skipConfirm = true;
await Promise.all(dirtyWorkingCopies.map(dirty => dirty.revert()));
} }
} }
@@ -296,7 +299,7 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, workingCopyF
skipConfirm = true; skipConfirm = true;
return deleteFiles(workingCopyService, workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm); return deleteFiles(workingCopyFileService, dialogService, configurationService, elements, useTrash, skipConfirm);
} }
} }
} }
@@ -989,7 +992,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService); const explorerService = accessor.get(IExplorerService);
const stats = explorerService.getContext(true).filter(s => !s.isRoot); const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) { if (stats.length) {
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true); await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
} }
}; };
@@ -998,7 +1001,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
const stats = explorerService.getContext(true).filter(s => !s.isRoot); const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) { if (stats.length) {
await deleteFiles(accessor.get(IWorkingCopyService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false); await deleteFiles(accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
} }
}; };
@@ -66,17 +66,6 @@
border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */ border-radius: 0; /* goes better when ellipsis shows up on narrow sidebar */
} }
.explorer-viewlet .explorer-empty-view {
padding: 0 20px 0 20px;
}
.explorer-viewlet .explorer-empty-view .monaco-button {
max-width: 260px;
margin-left: auto;
margin-right: auto;
display: block;
}
.explorer-viewlet .explorer-item.nonexistent-root { .explorer-viewlet .explorer-item.nonexistent-root {
opacity: 0.5; opacity: 0.5;
} }
@@ -4,13 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import * as DOM from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { OpenFolderAction, AddRootFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -20,10 +15,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie
import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { ResourcesDropHandler, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { listDropBackground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILabelService } from 'vs/platform/label/common/label'; import { ILabelService } from 'vs/platform/label/common/label';
import { Schemas } from 'vs/base/common/network';
import { isWeb } from 'vs/base/common/platform';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IOpenerService } from 'vs/platform/opener/common/opener';
@@ -33,9 +25,6 @@ export class EmptyView extends ViewPane {
static readonly ID: string = 'workbench.explorer.emptyView'; static readonly ID: string = 'workbench.explorer.emptyView';
static readonly NAME = nls.localize('noWorkspace', "No Folder Opened"); static readonly NAME = nls.localize('noWorkspace', "No Folder Opened");
private button!: Button;
private messageElement!: HTMLElement;
constructor( constructor(
options: IViewletViewOptions, options: IViewletViewOptions,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@@ -45,55 +34,31 @@ export class EmptyView extends ViewPane {
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@ILabelService private labelService: ILabelService, @ILabelService private labelService: ILabelService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@IOpenerService openerService: IOpenerService @IOpenerService openerService: IOpenerService
) { ) {
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService);
this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels()));
this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle()));
this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle()));
}
shouldShowWelcome(): boolean {
return true;
} }
protected renderBody(container: HTMLElement): void { protected renderBody(container: HTMLElement): void {
super.renderBody(container); super.renderBody(container);
DOM.addClass(container, 'explorer-empty-view');
container.tabIndex = 0;
const messageContainer = document.createElement('div');
DOM.addClass(messageContainer, 'section');
container.appendChild(messageContainer);
this.messageElement = document.createElement('p');
messageContainer.appendChild(this.messageElement);
this.button = new Button(messageContainer);
attachButtonStyler(this.button, this.themeService);
this._register(this.button.onDidClick(() => {
if (!this.actionRunner) {
return;
}
const action = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE
? this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL)
: this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
this.actionRunner.run(action).then(() => {
action.dispose();
}, err => {
action.dispose();
errors.onUnexpectedError(err);
});
}));
this._register(new DragAndDropObserver(container, { this._register(new DragAndDropObserver(container, {
onDrop: e => { onDrop: e => {
const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND);
container.style.backgroundColor = color ? color.toString() : ''; container.style.backgroundColor = color ? color.toString() : '';
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true });
dropHandler.handleDrop(e, () => undefined, targetGroup => undefined); dropHandler.handleDrop(e, () => undefined, () => undefined);
}, },
onDragEnter: (e) => { onDragEnter: () => {
const color = this.themeService.getTheme().getColor(listDropBackground); const color = this.themeService.getTheme().getColor(listDropBackground);
container.style.backgroundColor = color ? color.toString() : ''; container.style.backgroundColor = color ? color.toString() : '';
}, },
@@ -112,26 +77,13 @@ export class EmptyView extends ViewPane {
} }
})); }));
this.setLabels(); this.refreshTitle();
} }
private setLabels(): void { private refreshTitle(): void {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.messageElement.textContent = nls.localize('noWorkspaceHelp', "You have not yet added a folder to the workspace.");
if (this.button) {
this.button.label = nls.localize('addFolder', "Add Folder");
}
this.updateTitle(EmptyView.NAME); this.updateTitle(EmptyView.NAME);
} else { } else {
if (this.environmentService.configuration.remoteAuthority && !isWeb) {
const hostLabel = this.labelService.getHostLabel(Schemas.vscodeRemote, this.environmentService.configuration.remoteAuthority);
this.messageElement.textContent = hostLabel ? nls.localize('remoteNoFolderHelp', "Connected to {0}", hostLabel) : nls.localize('connecting', "Connecting...");
} else {
this.messageElement.textContent = nls.localize('noFolderHelp', "You have not yet opened a folder.");
}
if (this.button) {
this.button.label = nls.localize('openFolder', "Open Folder");
}
this.updateTitle(this.title); this.updateTitle(this.title);
} }
} }
@@ -139,9 +91,4 @@ export class EmptyView extends ViewPane {
layoutBody(_size: number): void { layoutBody(_size: number): void {
// no-op // no-op
} }
focus(): void {
this.button.element.focus();
}
} }
@@ -55,7 +55,6 @@ import { ILabelService } from 'vs/platform/label/common/label';
import { isNumber } from 'vs/base/common/types'; import { isNumber } from 'vs/base/common/types';
import { domEvent } from 'vs/base/browser/event'; import { domEvent } from 'vs/base/browser/event';
import { IEditableData } from 'vs/workbench/common/views'; import { IEditableData } from 'vs/workbench/common/views';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> { export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
@@ -643,8 +642,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
@IInstantiationService private instantiationService: IInstantiationService, @IInstantiationService private instantiationService: IInstantiationService,
@IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService, @IWorkingCopyFileService private workingCopyFileService: IWorkingCopyFileService,
@IHostService private hostService: IHostService, @IHostService private hostService: IHostService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
@IWorkingCopyService private workingCopyService: IWorkingCopyService
) { ) {
this.toDispose = []; this.toDispose = [];
@@ -945,15 +943,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
const sourceFile = resource; const sourceFile = resource;
const targetFile = joinPath(target.resource, basename(sourceFile)); const targetFile = joinPath(target.resource, basename(sourceFile));
// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents const stat = await this.workingCopyFileService.copy(sourceFile, targetFile, true);
// of the target file would replace the contents of the added file. since we already
// confirmed the overwrite before, this is OK.
if (this.workingCopyService.isDirty(targetFile)) {
await Promise.all(this.workingCopyService.getWorkingCopies(targetFile).map(workingCopy => workingCopy.revert({ soft: true })));
}
const copyTarget = joinPath(target.resource, basename(sourceFile));
const stat = await this.workingCopyFileService.copy(sourceFile, copyTarget, true);
// if we only add one file, just open it directly // if we only add one file, just open it directly
if (resources.length === 1 && !stat.isDirectory) { if (resources.length === 1 && !stat.isDirectory) {
this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor'; import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput } from 'vs/workbench/common/editor';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { ITextFileService, ModelState, LoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService';
@@ -56,6 +56,8 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
) { ) {
super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService); super(resource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
this.model = this.textFileService.files.get(resource);
if (preferredEncoding) { if (preferredEncoding) {
this.setPreferredEncoding(preferredEncoding); this.setPreferredEncoding(preferredEncoding);
} }
@@ -63,6 +65,11 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
if (preferredMode) { if (preferredMode) {
this.setPreferredMode(preferredMode); this.setPreferredMode(preferredMode);
} }
// If a file model already exists, make sure to wire it in
if (this.model) {
this.registerModelListeners(this.model);
}
} }
protected registerListeners(): void { protected registerListeners(): void {
@@ -98,10 +105,10 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire())); this.modelListeners.add(model.onDidSaveError(() => this._onDidChangeDirty.fire()));
// remove model association once it gets disposed // remove model association once it gets disposed
Event.once(model.onDispose)(() => { this.modelListeners.add(Event.once(model.onDispose)(() => {
this.modelListeners.clear(); this.modelListeners.clear();
this.model = undefined; this.model = undefined;
}); }));
} }
getEncoding(): string | undefined { getEncoding(): string | undefined {
@@ -167,7 +174,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
} }
private decorateLabel(label: string): string { private decorateLabel(label: string): string {
const orphaned = this.model?.hasState(ModelState.ORPHAN); const orphaned = this.model?.hasState(TextFileEditorModelState.ORPHAN);
const readonly = this.isReadonly(); const readonly = this.isReadonly();
if (orphaned && readonly) { if (orphaned && readonly) {
@@ -198,7 +205,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
} }
isSaving(): boolean { isSaving(): boolean {
if (this.model?.hasState(ModelState.SAVED) || this.model?.hasState(ModelState.CONFLICT) || this.model?.hasState(ModelState.ERROR)) { if (this.model?.hasState(TextFileEditorModelState.SAVED) || this.model?.hasState(TextFileEditorModelState.CONFLICT) || this.model?.hasState(TextFileEditorModelState.ERROR)) {
return false; // require the model to be dirty and not in conflict or error state return false; // require the model to be dirty and not in conflict or error state
} }
@@ -234,7 +241,7 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi
encoding: this.preferredEncoding, encoding: this.preferredEncoding,
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
allowBinary: this.forceOpenAs === ForceOpenAs.Text, allowBinary: this.forceOpenAs === ForceOpenAs.Text,
reason: LoadReason.EDITOR reason: TextFileLoadReason.EDITOR
}); });
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
@@ -28,10 +28,8 @@ export class LogViewerInput extends ResourceEditorInput {
static readonly ID = 'workbench.editorinputs.output'; static readonly ID = 'workbench.editorinputs.output';
readonly resource = this.outputChannelDescriptor.file;
constructor( constructor(
private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, outputChannelDescriptor: IFileOutputChannelDescriptor,
@ITextModelService textModelResolverService: ITextModelService, @ITextModelService textModelResolverService: ITextModelService,
@ITextFileService textFileService: ITextFileService, @ITextFileService textFileService: ITextFileService,
@IEditorService editorService: IEditorService, @IEditorService editorService: IEditorService,
@@ -13,16 +13,16 @@
} }
/* Deal with overflow */ /* Deal with overflow */
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value,
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
white-space: nowrap; white-space: pre;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-value { .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-value {
max-width: 90%; max-width: 90%;
} }
.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-widget .setting-list-sibling { .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-list .setting-list-sibling {
max-width: 10%; max-width: 10%;
} }
@@ -1640,12 +1640,13 @@ class StopSyncingSettingAction extends Action {
} }
async run(): Promise<void> { async run(): Promise<void> {
const currentValue = this.configService.getValue<string[]>('sync.ignoredSettings'); let currentValue = [...this.configService.getValue<string[]>('sync.ignoredSettings')];
if (this.checked) { if (this.checked) {
this.configService.updateValue('sync.ignoredSettings', currentValue.filter(v => v !== this.setting.key)); currentValue = currentValue.filter(v => v !== this.setting.key);
} else { } else {
this.configService.updateValue('sync.ignoredSettings', [...currentValue, this.setting.key]); currentValue.push(this.setting.key);
} }
this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER);
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
@@ -443,12 +443,12 @@ export class ListSettingWidget extends Disposable {
const onSubmit = (edited: boolean) => { const onSubmit = (edited: boolean) => {
this.model.setEditKey('none'); this.model.setEditKey('none');
const value = valueInput.value.trim(); const value = valueInput.value;
if (edited && !isUndefinedOrNull(value)) { if (edited && !isUndefinedOrNull(value)) {
this._onDidChangeList.fire({ this._onDidChangeList.fire({
originalValue: item.value, originalValue: item.value,
value: value, value: value,
sibling: siblingInput && siblingInput.value.trim(), sibling: siblingInput && siblingInput.value,
targetIndex: idx targetIndex: idx
}); });
} }
@@ -146,13 +146,16 @@ interface ResourceTemplate {
disposables: IDisposable; disposables: IDisposable;
} }
class MultipleSelectionActionRunner extends ActionRunner { class RepositoryPaneActionRunner extends ActionRunner {
constructor(private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[]) { constructor(
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
private focus: () => void
) {
super(); super();
} }
runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> { async runAction(action: IAction, context: ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>): Promise<any> {
if (!(action instanceof MenuItemAction)) { if (!(action instanceof MenuItemAction)) {
return super.runAction(action, context); return super.runAction(action, context);
} }
@@ -161,7 +164,8 @@ class MultipleSelectionActionRunner extends ActionRunner {
const contextIsSelected = selection.some(s => s === context); const contextIsSelected = selection.some(s => s === context);
const actualContext = contextIsSelected ? selection : [context]; const actualContext = contextIsSelected ? selection : [context];
const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e])); const args = flatten(actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]));
return action.run(...args); await action.run(...args);
this.focus();
} }
} }
@@ -175,6 +179,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
private labels: ResourceLabels, private labels: ResourceLabels,
private actionViewItemProvider: IActionViewItemProvider, private actionViewItemProvider: IActionViewItemProvider,
private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[], private getSelectedResources: () => (ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>)[],
private focus: () => void,
private themeService: IThemeService, private themeService: IThemeService,
private menus: SCMMenus private menus: SCMMenus
) { } ) { }
@@ -186,7 +191,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
const actionsContainer = append(fileLabel.element, $('.actions')); const actionsContainer = append(fileLabel.element, $('.actions'));
const actionBar = new ActionBar(actionsContainer, { const actionBar = new ActionBar(actionsContainer, {
actionViewItemProvider: this.actionViewItemProvider, actionViewItemProvider: this.actionViewItemProvider,
actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources) actionRunner: new RepositoryPaneActionRunner(this.getSelectedResources, this.focus)
}); });
const decorationIcon = append(element, $('.decoration-icon')); const decorationIcon = append(element, $('.decoration-icon'));
@@ -730,7 +735,8 @@ export class RepositoryPane extends ViewPane {
wrappingStrategy: 'advanced', wrappingStrategy: 'advanced',
wrappingIndent: 'none', wrappingIndent: 'none',
padding: { top: 3, bottom: 3 }, padding: { top: 3, bottom: 3 },
suggest: { showWords: false } suggest: { showWords: false },
quickSuggestions: false
}; };
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
@@ -820,7 +826,7 @@ export class RepositoryPane extends ViewPane {
const renderers = [ const renderers = [
new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus),
new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), () => this.tree.domFocus(), this.themeService, this.menus)
]; ];
const filter = new SCMTreeFilter(); const filter = new SCMTreeFilter();
@@ -1024,7 +1030,7 @@ export class RepositoryPane extends ViewPane {
getAnchor: () => e.anchor, getAnchor: () => e.anchor,
getActions: () => actions, getActions: () => actions,
getActionsContext: () => element, getActionsContext: () => element,
actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources()) actionRunner: new RepositoryPaneActionRunner(() => this.getSelectedResources(), () => this.tree.domFocus())
}); });
} }
@@ -104,14 +104,7 @@ export class ReplaceService implements IReplaceService {
const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource); const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource);
await this.bulkEditorService.apply({ edits }, { progress }); await this.bulkEditorService.apply({ edits }, { progress });
return Promise.all(edits.map(e => { return Promise.all(edits.map(e => this.textFileService.files.get(e.resource)?.save()));
const model = this.textFileService.files.get(e.resource);
if (model) {
return model.save();
}
return Promise.resolve(undefined);
}));
} }
async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> { async openReplacePreview(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
@@ -9,7 +9,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { IAction } from 'vs/base/common/actions'; import { IAction, ActionRunner } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async'; import { Delayer } from 'vs/base/common/async';
import * as errors from 'vs/base/common/errors'; import * as errors from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
@@ -213,7 +213,7 @@ export class SearchView extends ViewPane {
this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE);
this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e)));
this._register(this.textFileService.untitled.onDidDisposeModel(e => this.onUntitledDidDispose(e))); this._register(this.textFileService.untitled.onDidDispose(model => this.onUntitledDidDispose(model.resource)));
this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState())); this._register(this.contextService.onDidChangeWorkbenchState(() => this.onDidChangeWorkbenchState()));
this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory())); this._register(this.searchHistoryService.onDidClearHistory(() => this.clearHistory()));
@@ -1584,6 +1584,7 @@ export class SearchView extends ViewPane {
const openFolderLink = dom.append(textEl, const openFolderLink = dom.append(textEl,
$('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder"))); $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder")));
const actionRunner = new ActionRunner();
this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => { this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => {
dom.EventHelper.stop(e, false); dom.EventHelper.stop(e, false);
@@ -1591,7 +1592,7 @@ export class SearchView extends ViewPane {
this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) : this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) :
this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL);
this.actionRunner!.run(action).then(() => { actionRunner.run(action).then(() => {
action.dispose(); action.dispose();
}, err => { }, err => {
action.dispose(); action.dispose();
@@ -53,8 +53,7 @@ class LanguageSurvey extends Disposable {
// Process model-save event every 250ms to reduce load // Process model-save event every 250ms to reduce load
const onModelsSavedWorker = this._register(new RunOnceWorker<ITextFileEditorModel>(models => { const onModelsSavedWorker = this._register(new RunOnceWorker<ITextFileEditorModel>(models => {
models.forEach(m => { models.forEach(m => {
const model = modelService.getModel(m.resource); if (m.getMode() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) {
const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1;
storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL);
storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL);
@@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ITextFileService, ITextFileModelSaveEvent, ITextFileModelLoadEvent } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { extname, basename, isEqual, isEqualOrParent, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
@@ -58,7 +58,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IViewletService viewletService: IViewletService, @IViewletService viewletService: IViewletService,
@ITextFileService textFileService: ITextFileService, @ITextFileService textFileService: ITextFileService
) { ) {
super(); super();
@@ -131,7 +131,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
this._register(lifecycleService.onShutdown(() => this.dispose())); this._register(lifecycleService.onShutdown(() => this.dispose()));
} }
private onTextFileModelLoaded(e: ITextFileModelLoadEvent): void { private onTextFileModelLoaded(e: ITextFileLoadEvent): void {
const settingsType = this.getTypeIfSettings(e.model.resource); const settingsType = this.getTypeIfSettings(e.model.resource);
if (settingsType) { if (settingsType) {
type SettingsReadClassification = { type SettingsReadClassification = {
@@ -146,7 +146,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
} }
} }
private onTextFileModelSaved(e: ITextFileModelSaveEvent): void { private onTextFileModelSaved(e: ITextFileSaveEvent): void {
const settingsType = this.getTypeIfSettings(e.model.resource); const settingsType = this.getTypeIfSettings(e.model.resource);
if (settingsType) { if (settingsType) {
type SettingsWrittenClassification = { type SettingsWrittenClassification = {
@@ -690,6 +690,8 @@ export class RunActiveFileInTerminalAction extends Action {
if (!instance) { if (!instance) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
await instance.processReady;
const editor = this.codeEditorService.getActiveCodeEditor(); const editor = this.codeEditorService.getActiveCodeEditor();
if (!editor || !editor.hasModel()) { if (!editor || !editor.hasModel()) {
return Promise.resolve(undefined); return Promise.resolve(undefined);
@@ -21,7 +21,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { IMenuItem, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey, ContextKeyRegexExpr } from 'vs/platform/contextkey/common/contextkey';
@@ -31,7 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource } from 'vs/platform/userDataSync/common/userDataSync'; import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync';
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -52,7 +52,6 @@ const enum AuthStatus {
SignedOut = 'SignedOut', SignedOut = 'SignedOut',
Unavailable = 'Unavailable' Unavailable = 'Unavailable'
} }
const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing); const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', ''); const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
@@ -547,7 +546,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
} else { } else {
await this.userDataSyncService.resetLocal(); await this.userDataSyncService.resetLocal();
} }
await this.signOut();
this.disableSync(); this.disableSync();
} }
} }
@@ -574,13 +572,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
} }
} }
private async signOut(): Promise<void> {
if (this.activeAccount) {
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id);
await this.setActiveAccount(undefined);
}
}
private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined {
const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource
: source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource
@@ -652,6 +643,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}, },
when: turnOnSyncWhenContext, when: turnOnSyncWhenContext,
}); });
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '5_sync',
command: {
id: turnOnSyncCommandId,
title: localize('global activity turn on sync', "Turn on Sync...")
},
when: turnOnSyncWhenContext,
});
const signInCommandId = 'workbench.userData.actions.signin'; const signInCommandId = 'workbench.userData.actions.signin';
const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)); const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut));
@@ -697,6 +696,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}, },
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
}); });
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
group: '5_sync',
command: {
id: stopSyncCommandId,
title: localize('global activity stop sync', "Turn off Sync")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT),
});
const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts'; const resolveSettingsConflictsCommandId = 'workbench.userData.actions.resolveSettingsConflicts';
const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i); const resolveSettingsConflictsWhenContext = ContextKeyRegexExpr.create(CONTEXT_CONFLICTS_SOURCES.keys()[0], /.*settings.*/i);
@@ -736,17 +743,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
when: resolveKeybindingsConflictsWhenContext, when: resolveKeybindingsConflictsWhenContext,
}); });
const signOutMenuItem: IMenuItem = {
group: '5_sync',
command: {
id: 'workbench.userData.actions.signout',
title: localize('sign out', "Sync: Sign out")
},
when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn)),
};
CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem);
const configureSyncCommandId = 'workbench.userData.actions.configureSync'; const configureSyncCommandId = 'workbench.userData.actions.configureSync';
CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions()); CommandsRegistry.registerCommand(configureSyncCommandId, () => this.configureSyncOptions());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
@@ -767,15 +763,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
}); });
const resetLocalCommandId = 'workbench.userData.actions.resetLocal';
CommandsRegistry.registerCommand(resetLocalCommandId, () => this.userDataSyncService.resetLocal());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: resetLocalCommandId,
title: localize('reset local', "Developer: Reset Local (Sync)")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)),
});
} }
} }
@@ -26,10 +26,12 @@ export class WebviewInput extends EditorInput {
private readonly _onDisposeWebview = this._register(new Emitter<void>()); private readonly _onDisposeWebview = this._register(new Emitter<void>());
readonly onDisposeWebview = this._onDisposeWebview.event; readonly onDisposeWebview = this._onDisposeWebview.event;
readonly resource = URI.from({ get resource() {
scheme: WebviewPanelResourceScheme, return URI.from({
path: `webview-panel/webview-${this.id}` scheme: WebviewPanelResourceScheme,
}); path: `webview-panel/webview-${this.id}`
});
}
constructor( constructor(
public readonly id: string, public readonly id: string,
@@ -53,10 +53,10 @@ export class WalkThroughInput extends EditorInput {
private maxTopScroll = 0; private maxTopScroll = 0;
private maxBottomScroll = 0; private maxBottomScroll = 0;
readonly resource = this.options.resource; get resource() { return this.options.resource; }
constructor( constructor(
private options: WalkThroughInputOptions, private readonly options: WalkThroughInputOptions,
@ITextModelService private readonly textModelResolverService: ITextModelService @ITextModelService private readonly textModelResolverService: ITextModelService
) { ) {
super(); super();

Some files were not shown because too many files have changed in this diff Show More