Merge remote-tracking branch 'origin/master' into joao/explorer-compressed-tree

This commit is contained in:
Joao Moreno 2019-11-14 12:39:29 +01:00
commit 8667883738
91 changed files with 820 additions and 540 deletions

11
.github/commands.yml vendored
View file

@ -118,10 +118,10 @@
},
{
type: 'label',
name: '*needs more info',
name: '~needs more info',
action: 'updateLabels',
addLabel: 'needs more info',
removeLabel: '*needs more info',
removeLabel: '~needs more info',
comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!"
},
{
@ -131,12 +131,5 @@
action: 'updateLabels',
addLabel: 'a11ymas'
},
{
type: 'label',
name: '*needs more info',
action: 'updateLabels',
addLabel: 'needs more info',
removeLabel: '*needs more info'
},
]
}

View file

@ -1,7 +1,7 @@
[
{
"name": "ms-vscode.node-debug",
"version": "1.40.1",
"version": "1.41.0",
"repo": "https://github.com/Microsoft/vscode-node-debug",
"metadata": {
"id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6",
@ -16,7 +16,7 @@
},
{
"name": "ms-vscode.node-debug2",
"version": "1.39.3",
"version": "1.41.0",
"repo": "https://github.com/Microsoft/vscode-node-debug2",
"metadata": {
"id": "36d19e17-7569-4841-a001-947eb18602b2",
@ -31,7 +31,7 @@
},
{
"name": "ms-vscode.references-view",
"version": "0.0.35",
"version": "0.0.36",
"repo": "https://github.com/Microsoft/vscode-reference-view",
"metadata": {
"id": "dc489f46-520d-4556-ae85-1f9eab3c412d",

View file

@ -7,7 +7,7 @@
"config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
"config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts when there is no top-level 'package.json' file.",
"config.npm.scriptExplorerAction": "The default click action used in the npm scripts explorer: `open` or `run`, the default is `open`.",
"config.npm.enableRunFromFolder": "Enable running NPM scripts contained in a folder from the Explorer context menu.",
"config.npm.enableRunFromFolder": "Enable running npm scripts contained in a folder from the Explorer context menu.",
"config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs.org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.",
"npm.parseError": "Npm task detection: failed to parse the file {0}",
"taskdef.script": "The npm script to customize.",

View file

@ -82,7 +82,8 @@ namespace ServerState {
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`;
private pathSeparator: string;
private readonly pathSeparator: string;
private readonly inMemoryResourcePrefix = '^';
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void; };
private _configuration: TypeScriptServiceConfiguration;
@ -591,23 +592,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return this.toPath(document.uri) || undefined;
}
private get inMemoryResourcePrefix(): string {
return this.apiVersion.gte(API.v270) ? '^' : '';
}
public toResource(filepath: string): vscode.Uri {
if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':'))
) {
let resource = vscode.Uri.parse(filepath);
if (this.inMemoryResourcePrefix) {
const dirName = path.dirname(resource.path);
const fileName = path.basename(resource.path);
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
resource = resource.with({
path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length))
});
}
const dirName = path.dirname(resource.path);
const fileName = path.basename(resource.path);
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
resource = resource.with({
path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length))
});
}
return this.bufferSyncSupport.toVsCodeResource(resource);
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.41.0",
"distro": "be6ad88ea0214dfdb03b943bef28e7a5c9fc2e4b",
"distro": "403ab44be562c63a0cde1969fd8f5b45ff51709c",
"author": {
"name": "Microsoft Corporation"
},
@ -42,10 +42,10 @@
"native-keymap": "2.0.0",
"native-watchdog": "1.2.0",
"node-pty": "^0.10.0-beta2",
"onigasm-umd": "^2.2.2",
"onigasm-umd": "^2.2.4",
"semver-umd": "^5.5.3",
"spdlog": "^0.11.1",
"sudo-prompt": "9.0.0",
"sudo-prompt": "9.1.0",
"v8-inspect-profiler": "^0.0.20",
"vscode-minimist": "^1.2.1",
"vscode-nsfw": "1.2.8",

View file

@ -12,7 +12,7 @@
"jschardet": "2.1.1",
"native-watchdog": "1.2.0",
"node-pty": "^0.10.0-beta2",
"onigasm-umd": "^2.2.2",
"onigasm-umd": "^2.2.4",
"semver-umd": "^5.5.3",
"spdlog": "^0.11.1",
"vscode-minimist": "^1.2.1",

View file

@ -2,7 +2,7 @@
"name": "vscode-web",
"version": "0.0.0",
"dependencies": {
"onigasm-umd": "^2.2.2",
"onigasm-umd": "^2.2.4",
"semver-umd": "^5.5.3",
"vscode-textmate": "^4.3.0",
"xterm": "4.3.0-beta17",

View file

@ -7,10 +7,10 @@ nan@^2.14.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
onigasm-umd@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
onigasm-umd@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec"
integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg==
oniguruma@^7.2.0:
version "7.2.0"

View file

@ -283,10 +283,10 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
onigasm-umd@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
onigasm-umd@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec"
integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg==
oniguruma@^7.2.0:
version "7.2.0"

View file

@ -13,9 +13,8 @@ set NAMESHORT=%NAMESHORT: "=%
set NAMESHORT=%NAMESHORT:"=%.exe
set CODE=".build\electron\%NAMESHORT%"
:: Download Electron if needed
node build\lib\electron.js
if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron
:: Get electron
call yarn electron
:: Manage built-in extensions
if "%1"=="--builtin" goto builtin

View file

@ -1,9 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'sudo-prompt' {
export function exec(cmd: string, options: { name?: string, icns?: string }, callback: (error: string, stdout: string, stderr: string) => void): void;
}

View file

@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?3443fb4013ab5a04c23a30f912dd6627") format("truetype");
src: url("./codicon.ttf?72bd9e6bbf1e48287bcb9a9e4babeb28") format("truetype");
}
.codicon[class*='codicon-'] {
@ -385,4 +385,5 @@
.codicon-list-filter:before { content: "\eb83" }
.codicon-list-flat:before { content: "\eb84" }
.codicon-list-selection:before { content: "\eb85" }
.codicon-selection:before { content: "\eb85" }
.codicon-list-tree:before { content: "\eb86" }

View file

@ -267,10 +267,10 @@ export class URI implements UriComponents {
}
return new _URI(
match[2] || _empty,
decodeURIComponent(match[4] || _empty),
decodeURIComponent(match[5] || _empty),
decodeURIComponent(match[7] || _empty),
decodeURIComponent(match[9] || _empty),
percentDecode(match[4] || _empty),
percentDecode(match[5] || _empty),
percentDecode(match[7] || _empty),
percentDecode(match[9] || _empty),
_strict
);
}
@ -648,3 +648,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string {
}
return res;
}
// --- decode
function decodeURIComponentGraceful(str: string): string {
try {
return decodeURIComponent(str);
} catch {
if (str.length > 3) {
return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3));
} else {
return str;
}
}
}
const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g;
function percentDecode(str: string): string {
if (!str.match(_rEncodedAsHex)) {
return str;
}
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
}

View file

@ -439,6 +439,10 @@ suite('URI', () => {
assert.equal(uri.path, uri2.path);
});
test('Unable to open \'%A0.txt\': URI malformed #76506', function () {
assert.equal(URI.parse('file://some/%.txt'), 'file://some/%25.txt');
assert.equal(URI.parse('file://some/%A0.txt'), 'file://some/%25A0.txt');
});
test('Links in markdown are broken if url contains encoded parameters #79474', function () {
this.skip();

View file

@ -16,7 +16,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@ -303,7 +303,7 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo
EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
}
export function registerEditorContribution(id: string, ctor: IEditorContributionCtor): void {
export function registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor);
}
@ -355,7 +355,7 @@ class EditorContributionRegistry {
this.editorCommands = Object.create(null);
}
public registerEditorContribution(id: string, ctor: IEditorContributionCtor): void {
public registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
this.editorContributions.push({ id, ctor });
}

View file

@ -5,7 +5,6 @@
import 'vs/css!./codelensWidget';
import * as dom from 'vs/base/browser/dom';
import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays';
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range';
@ -66,7 +65,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
constructor(
editor: editorBrowser.ICodeEditor,
symbolRange: Range,
data: CodeLensItem[]
lenses: Array<CodeLens | undefined | null>
) {
this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool);
this._editor = editor;
@ -74,10 +73,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
this.setSymbolRange(symbolRange);
this._domNode = document.createElement('span');
this._domNode.innerHTML = '&nbsp;';
dom.addClass(this._domNode, 'codelens-decoration');
this._domNode.className = 'codelens-decoration';
this.updateHeight();
this.withCommands(data.map(data => data.symbol), false);
this.withCommands(lenses, false);
}
updateHeight(): void {
@ -88,39 +86,45 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
this._domNode.style.lineHeight = `${lineHeight}px`;
this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`;
this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`;
this._domNode.innerHTML = '&nbsp;';
}
withCommands(inSymbols: Array<CodeLens | undefined | null>, animate: boolean): void {
withCommands(lenses: Array<CodeLens | undefined | null>, animate: boolean): void {
this._commands.clear();
const symbols = coalesce(inSymbols);
if (isFalsyOrEmpty(symbols)) {
this._domNode.innerHTML = '<span>no commands</span>';
return;
}
let html: string[] = [];
for (let i = 0; i < symbols.length; i++) {
const command = symbols[i].command;
if (command) {
const title = renderCodicons(command.title);
let part: string;
if (command.id) {
part = `<a id=${i}>${title}</a>`;
this._commands.set(String(i), command);
let innerHtml = '';
let hasSymbol = false;
for (let i = 0; i < lenses.length; i++) {
const lens = lenses[i];
if (!lens) {
continue;
}
hasSymbol = true;
if (lens.command) {
const title = renderCodicons(lens.command.title);
if (lens.command.id) {
innerHtml += `<a id=${i}>${title}</a>`;
this._commands.set(String(i), lens.command);
} else {
part = `<span>${title}</span>`;
innerHtml += `<span>${title}</span>`;
}
if (i + 1 < lenses.length) {
innerHtml += '<span>&nbsp;|&nbsp;</span>';
}
html.push(part);
}
}
const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === '&nbsp;';
this._domNode.innerHTML = html.join('<span>&nbsp;|&nbsp;</span>');
this._editor.layoutContentWidget(this);
if (wasEmpty && animate) {
dom.addClass(this._domNode, 'fadein');
if (!hasSymbol) {
// symbols but no commands
this._domNode.innerHTML = '<span>no commands</span>';
} else {
// symbols and commands
const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === '&nbsp;';
this._domNode.innerHTML = innerHtml || '&nbsp;';
this._editor.layoutContentWidget(this);
if (wasEmpty && animate) {
dom.addClass(this._domNode, 'fadein');
}
}
}
@ -213,8 +217,11 @@ export class CodeLensWidget {
this._decorationIds = new Array<string>(this._data.length);
let range: Range | undefined;
let lenses: CodeLens[] = [];
this._data.forEach((codeLensData, i) => {
lenses.push(codeLensData.symbol);
helper.addDecoration({
range: codeLensData.symbol.range,
options: ModelDecorationOptions.EMPTY
@ -229,7 +236,7 @@ export class CodeLensWidget {
});
if (range) {
this._contentWidget = new CodeLensContentWidget(editor, range, this._data);
this._contentWidget = new CodeLensContentWidget(editor, range, lenses);
this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback);
this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone);

View file

@ -13,12 +13,12 @@
.monaco-editor .parameter-hints-widget > .wrapper {
max-width: 440px;
display: flex;
flex-direction: column;
flex-direction: row;
}
.monaco-editor .parameter-hints-widget.multiple {
min-height: 3.3em;
padding: 0 0 0 1.9em;
padding: 0;
}
.monaco-editor .parameter-hints-widget.visible {
@ -62,20 +62,19 @@
padding: 0 0.4em;
}
.monaco-editor .parameter-hints-widget .buttons {
position: absolute;
.monaco-editor .parameter-hints-widget .controls {
display: none;
bottom: 0;
left: 0;
flex-direction: column;
align-items: center;
min-width: 22px;
justify-content: flex-end;
}
.monaco-editor .parameter-hints-widget.multiple .buttons {
display: block;
.monaco-editor .parameter-hints-widget.multiple .controls {
display: flex;
}
.monaco-editor .parameter-hints-widget.multiple .button {
position: absolute;
left: 2px;
width: 16px;
height: 16px;
background-repeat: no-repeat;
@ -88,26 +87,16 @@
}
.monaco-editor .parameter-hints-widget .button.next {
bottom: 0;
background-image: url('arrow-down.svg');
}
.monaco-editor .parameter-hints-widget .overloads {
position: absolute;
display: none;
text-align: center;
bottom: 14px;
left: 0;
width: 22px;
height: 12px;
line-height: 12px;
opacity: 0.5;
}
.monaco-editor .parameter-hints-widget.multiple .overloads {
display: block;
}
.monaco-editor .parameter-hints-widget .signature .parameter.active {
font-weight: bold;
text-decoration: underline;

View file

@ -22,6 +22,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry';
import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel';
import { pad } from 'vs/base/common/strings';
const $ = dom.$;
@ -76,9 +77,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
const wrapper = dom.append(element, $('.wrapper'));
wrapper.tabIndex = -1;
const buttons = dom.append(wrapper, $('.buttons'));
const previous = dom.append(buttons, $('.button.previous'));
const next = dom.append(buttons, $('.button.next'));
const controls = dom.append(wrapper, $('.controls'));
const previous = dom.append(controls, $('.button.previous'));
const overloads = dom.append(controls, $('.overloads'));
const next = dom.append(controls, $('.button.next'));
const onPreviousClick = stop(domEvent(previous, 'click'));
this._register(onPreviousClick(this.previous, this));
@ -86,8 +88,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
const onNextClick = stop(domEvent(next, 'click'));
this._register(onNextClick(this.next, this));
const overloads = dom.append(wrapper, $('.overloads'));
const body = $('.body');
const scrollbar = new DomScrollableElement(body, {});
this._register(scrollbar);
@ -239,12 +239,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget {
dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs);
dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs);
let currentOverload = String(hints.activeSignature + 1);
if (hints.signatures.length < 10) {
currentOverload += `/${hints.signatures.length}`;
}
this.domNodes.overloads.textContent = currentOverload;
this.domNodes.overloads.textContent =
pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length;
if (activeParameter) {
const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter);

View file

@ -141,7 +141,6 @@ export interface IOpenDialogOptions {
availableFileSystems?: readonly string[];
}
export const IDialogService = createDecorator<IDialogService>('dialogService');
export interface IDialogOptions {
@ -240,12 +239,23 @@ export interface IFileDialogService {
*/
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
/**
* Shows a confirm dialog for saving 1-N files.
*/
showSaveConfirm(fileNameOrResources: string | URI[]): Promise<ConfirmResult>;
/**
* Shows a open file dialog and returns the chosen file URI.
*/
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
}
export const enum ConfirmResult {
SAVE,
DONT_SAVE,
CANCEL
}
const MAX_CONFIRM_FILES = 10;
export function getConfirmMessage(start: string, resourcesToConfirm: readonly URI[]): string {
const message = [start];

View file

@ -85,6 +85,7 @@ export interface ParsedArgs {
'js-flags'?: string;
'disable-gpu'?: boolean;
'nolazy'?: boolean;
'force-device-scale-factor'?: string;
}
export const IEnvironmentService = createDecorator<IEnvironmentService>('environmentService');

View file

@ -117,6 +117,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
'inspect': { type: 'string' },
'inspect-brk': { type: 'string' },
'nolazy': { type: 'boolean' }, // node inspect
'force-device-scale-factor': { type: 'string' },
'_urls': { type: 'string[]' },
_: { type: 'string[]' } // main arguments

View file

@ -362,7 +362,7 @@ export class FileService extends Disposable implements IFileService {
// mtime and etag, we bail out to prevent dirty writing.
//
// First, we check for a mtime that is in the future before we do more checks. The assumption is
// that only the mtime is an indicator for a file that has changd on disk.
// that only the mtime is an indicator for a file that has changed on disk.
//
// Second, if the mtime has advanced, we compare the size of the file on disk with our previous
// one using the etag() function. Relying only on the mtime check has prooven to produce false

View file

@ -134,7 +134,7 @@ suite('Disk File Service', function () {
// we see random test failures when accessing the native file system. To
// diagnose further, we retry node.js file access tests up to 3 times to
// rule out any random disk issue.
// this.retries(3);
this.retries(3);
setup(async () => {
const logService = new NullLogService();

View file

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { SyncDescriptor } from './descriptors';
import { ServiceIdentifier, IConstructorSignature0 } from './instantiation';
import { ServiceIdentifier, BrandedService } from './instantiation';
const _registry: [ServiceIdentifier<any>, SyncDescriptor<any>][] = [];
export function registerSingleton<T>(id: ServiceIdentifier<T>, ctor: IConstructorSignature0<T>, supportsDelayedInstantiation?: boolean): void {
export function registerSingleton<T, Services extends BrandedService[]>(id: ServiceIdentifier<T>, ctor: { new(...services: Services): T }, supportsDelayedInstantiation?: boolean): void {
_registry.push([id, new SyncDescriptor<T>(ctor, [], supportsDelayedInstantiation)]);
}

View file

@ -22,7 +22,7 @@ export namespace _util {
// --- interfaces ------
type BrandedService = { _serviceBrand: undefined };
export type BrandedService = { _serviceBrand: undefined };
export interface IConstructorSignature0<T> {
new(...services: BrandedService[]): T;
@ -101,7 +101,8 @@ export interface IInstantiationService {
createInstance<A1, A2, A3, A4, A5, A6, A7, T>(descriptor: descriptors.SyncDescriptor7<A1, A2, A3, A4, A5, A6, A7, T>, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T;
createInstance<A1, A2, A3, A4, A5, A6, A7, A8, T>(descriptor: descriptors.SyncDescriptor8<A1, A2, A3, A4, A5, A6, A7, A8, T>, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T;
createInstance<Ctor extends new (...args: any) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
createInstance<Services extends BrandedService[], Ctor extends new (...services: Services) => any, R extends InstanceType<Ctor>>(t: Ctor): R;
/**
*

View file

@ -33,7 +33,7 @@ export function registerConfiguration(): IDisposable {
'configurationSync.enable': {
type: 'boolean',
description: localize('configurationSync.enable', "When enabled, synchronizes configuration that includes Settings and Extensions."),
default: true,
default: false,
scope: ConfigurationScope.APPLICATION
},
'configurationSync.enableSettings': {
@ -63,12 +63,6 @@ export function registerConfiguration(): IDisposable {
$ref: ignoredSettingsSchemaId,
additionalProperties: true,
uniqueItems: true
},
'configurationSync.enableAuth': {
'type': 'boolean',
description: localize('configurationSync.enableAuth', "Enables authentication and requires VS Code restart when changed"),
'default': false,
'scope': ConfigurationScope.APPLICATION
}
}
});

View file

@ -496,7 +496,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Remember in recent document list (unless this opens for extension development)
// Also do not add paths when files are opened for diffing, only if opened individually
if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !(fileInputs?.filesToDiff) && !openConfig.noRecentEntry) {
const isDiff = fileInputs && fileInputs.filesToDiff.length > 0;
if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) {
const recents: IRecent[] = [];
for (let pathToOpen of pathsToOpen) {
if (pathToOpen.workspace) {

24
src/vs/vscode.d.ts vendored
View file

@ -2967,6 +2967,17 @@ declare module 'vscode' {
*/
appendPlaceholder(value: string | ((snippet: SnippetString) => any), number?: number): SnippetString;
/**
* Builder-function that appends a choice (`${1|a,b,c}`) to
* the [`value`](#SnippetString.value) of this snippet string.
*
* @param values The values for choices - the array of strings
* @param number The number of this tabstop, defaults to an auto-increment
* value starting at 1.
* @return This snippet string.
*/
appendChoice(values: string[], number?: number): SnippetString;
/**
* Builder-function that appends a variable (`${VAR}`) to
* the [`value`](#SnippetString.value) of this snippet string.
@ -5732,10 +5743,18 @@ declare module 'vscode' {
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*
* *Note:* If the file changed, it is important to provide an updated `mtime` that advanced
* from the previous value. Otherwise there may be optimizations in place that will not show
* the updated file contents in an editor for example.
*/
mtime: number;
/**
* The size in bytes.
*
* *Note:* If the file changed, it is important to provide an updated `size`. Otherwise there
* may be optimizations in place that will not show the updated file contents in an editor for
* example.
*/
size: number;
}
@ -5849,6 +5868,11 @@ declare module 'vscode' {
* An event to signal that a resource has been created, changed, or deleted. This
* event should fire for resources that are being [watched](#FileSystemProvider.watch)
* by clients of this provider.
*
* *Note:* It is important that the metadata of the file that changed provides an
* updated `mtime` that advanced from the previous value in the [stat](#FileStat) and a
* correct `size` value. Otherwise there may be optimizations in place that will not show
* the change in an editor for example.
*/
readonly onDidChangeFile: Event<FileChangeEvent[]>;

View file

@ -523,6 +523,27 @@ declare module 'vscode' {
//#region André: debug
/**
* A DebugSource is an opaque stand-in type for the type [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) defined in the Debug Adapter Protocol.
*/
export interface DebugSource {
// opaque contents
}
export namespace debug {
/**
* Converts a "Source" object received via the Debug Adapter Protocol into a Uri that can be used to load its contents.
*
* If the "Source" object has insufficient information to create a uri, an error is thrown.
*
* @param source An object conforming to the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol.
* @param session An optional debug session that will be used to locate the Debug Adapter Protocol.
* @return A uri that can be used to load the contents of the source.
*/
function asDebugSourceUri(source: DebugSource, session?: DebugSession): Uri;
}
// deprecated
export interface DebugConfigurationProvider {

View file

@ -778,7 +778,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
if (!parentSessionOrOptions || (typeof parentSessionOrOptions === 'object' && 'configuration' in parentSessionOrOptions)) {
return extHostDebugService.startDebugging(folder, nameOrConfig, { parentSession: parentSessionOrOptions });
}
checkProposedApiEnabled(extension);
return extHostDebugService.startDebugging(folder, nameOrConfig, parentSessionOrOptions || {});
},
addBreakpoints(breakpoints: vscode.Breakpoint[]) {
@ -786,6 +785,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
},
removeBreakpoints(breakpoints: vscode.Breakpoint[]) {
return extHostDebugService.removeBreakpoints(breakpoints);
},
asDebugSourceUri(source: vscode.DebugSource, session?: vscode.DebugSession): vscode.Uri {
checkProposedApiEnabled(extension);
return extHostDebugService.asDebugSourceUri(source, session);
}
};

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
import { IConstructorSignature1, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
@ -13,12 +13,12 @@ export type IExtHostNamedCustomer<T extends IDisposable> = [ProxyIdentifier<T>,
export type IExtHostCustomerCtor<T extends IDisposable> = IConstructorSignature1<IExtHostContext, T>;
export function extHostNamedCustomer<T extends IDisposable>(id: ProxyIdentifier<T>) {
return function (ctor: IExtHostCustomerCtor<T>): void {
return function <Services extends BrandedService[]>(ctor: { new(context: IExtHostContext, ...services: Services): T }): void {
ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor);
};
}
export function extHostCustomer<T extends IDisposable>(ctor: IExtHostCustomerCtor<T>): void {
export function extHostCustomer<T extends IDisposable, Services extends BrandedService[]>(ctor: { new(context: IExtHostContext, ...services: Services): T }): void {
ExtHostCustomersRegistryImpl.INSTANCE.registerCustomer(ctor);
}

View file

@ -30,5 +30,6 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape {
registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable;
registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable;
registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable;
asDebugSourceUri(source: vscode.DebugSource, session?: vscode.DebugSession): vscode.Uri;
}

View file

@ -740,6 +740,18 @@ export class SnippetString {
return this;
}
appendChoice(values: string[], number: number = this._tabstop++): SnippetString {
const value = SnippetString._escape(values.toString());
this.value += '${';
this.value += number;
this.value += '|';
this.value += value;
this.value += '|}';
return this;
}
appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString {
if (typeof defaultValue === 'function') {

View file

@ -138,6 +138,32 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe
});
}
public asDebugSourceUri(src: vscode.DebugSource, session?: vscode.DebugSession): URI {
const source = <any>src;
if (typeof source.sourceReference === 'number') {
// src can be retrieved via DAP's "source" request
let debug = `debug:${encodeURIComponent(source.path || '')}`;
let sep = '?';
if (session) {
debug += `${sep}session=${encodeURIComponent(session.id)}`;
sep = '&';
}
debug += `${sep}ref=${source.sourceReference}`;
return URI.parse(debug);
} else if (source.path) {
// src is just a local file path
return URI.file(source.path);
} else {
throw new Error(`cannot create uri from DAP 'source' object; properties 'path' and 'sourceReference' are both missing.`);
}
}
private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) {
const debugTypes: string[] = [];

View file

@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IAction } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
/**
* The action bar contributor allows to add actions to an actionbar in a given context.
@ -134,7 +134,7 @@ export interface IActionBarRegistry {
* Registers an Actionbar contributor. It will be called to contribute actions to all the action bars
* that are used in the Workbench in the given scope.
*/
registerActionBarContributor(scope: string, ctor: IConstructorSignature0<ActionBarContributor>): void;
registerActionBarContributor<Services extends BrandedService[]>(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void;
/**
* Returns an array of registered action bar contributors known to the workbench for the given scope.

View file

@ -368,7 +368,7 @@ class ResourceLabelWidget extends IconLabel {
setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void {
this.setResource({
resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }),
name: withNullAsUndefined(editor.getName()),
name: editor.getName(),
description: editor.getDescription(options ? options.descriptionVerbosity : undefined)
}, options);
}

View file

@ -95,6 +95,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
private readonly _onCenteredLayoutChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onCenteredLayoutChange: Event<boolean> = this._onCenteredLayoutChange.event;
private readonly _onMaximizeChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onMaximizeChange: Event<boolean> = this._onMaximizeChange.event;
private readonly _onPanelPositionChange: Emitter<string> = this._register(new Emitter<string>());
readonly onPanelPositionChange: Event<string> = this._onPanelPositionChange.event;
@ -142,6 +145,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
protected readonly state = {
fullscreen: false,
maximized: false,
hasFocus: false,
windowBorder: false,
@ -304,6 +308,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// Propagate to grid
this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART));
this.updateWindowBorder(true);
this.layout(); // handle title bar when fullscreen changes
}
@ -399,7 +405,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER);
let windowBorder = false;
if (activeBorder || inactiveBorder) {
if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) {
windowBorder = true;
// If one color is missing, just fallback to the other one
@ -1250,6 +1256,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._onPanelPositionChange.fire(positionToString(this.state.panel.position));
}
isWindowMaximized() {
return this.state.maximized;
}
updateWindowMaximizedState(maximized: boolean) {
if (this.state.maximized === maximized) {
return;
}
this.state.maximized = maximized;
this.updateWindowBorder();
this._onMaximizeChange.fire(maximized);
}
private createGridDescriptor(): ISerializedGrid {
const workbenchDimensions = this.getClientArea();
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width);

View file

@ -56,9 +56,8 @@ body.web {
width: 100vw;
}
.monaco-workbench.border {
height: calc(100vh - 2px);
width: calc(100vw - 2px);
.monaco-workbench.border:not(.fullscreen) {
box-sizing: border-box;
border: 1px solid var(--window-border-color);
}

View file

@ -46,7 +46,7 @@ export abstract class Part extends Component implements ISerializableView {
private options: IPartOptions,
themeService: IThemeService,
storageService: IStorageService,
layoutService: IWorkbenchLayoutService
protected readonly layoutService: IWorkbenchLayoutService
) {
super(id, themeService, storageService);

View file

@ -83,7 +83,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
constructor(
@IViewletService private readonly viewletService: IViewletService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@IThemeService themeService: IThemeService,
@IStorageService private readonly storageService: IStorageService,
@IExtensionService private readonly extensionService: IExtensionService,

View file

@ -56,7 +56,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
this.callbacks = callbacks;
}
getTitle() {
getTitle(): string {
return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer");
}

View file

@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { mixin } from 'vs/base/common/objects';
import { IEditorInput, EditorInput, IEditorIdentifier, ConfirmResult, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor';
import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor';
import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
@ -22,6 +22,8 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
export class ExecuteCommandAction extends Action {
@ -597,6 +599,8 @@ export abstract class BaseCloseAllAction extends Action {
label: string,
clazz: string | undefined,
private textFileService: ITextFileService,
private workingCopyService: IWorkingCopyService,
private fileDialogService: IFileDialogService,
protected editorGroupService: IEditorGroupsService
) {
super(id, label, clazz);
@ -619,7 +623,7 @@ export abstract class BaseCloseAllAction extends Action {
async run(): Promise<any> {
// Just close all if there are no dirty editors
if (!this.textFileService.isDirty()) {
if (!this.workingCopyService.hasDirty) {
return this.doCloseAll();
}
@ -636,7 +640,7 @@ export abstract class BaseCloseAllAction extends Action {
return undefined;
}));
const confirm = await this.textFileService.confirmSave();
const confirm = await this.fileDialogService.showSaveConfirm(this.workingCopyService.getDirty().map(copy => copy.resource));
if (confirm === ConfirmResult.CANCEL) {
return;
}
@ -667,9 +671,11 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
id: string,
label: string,
@ITextFileService textFileService: ITextFileService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IFileDialogService fileDialogService: IFileDialogService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, 'codicon-close-all', textFileService, editorGroupService);
super(id, label, 'codicon-close-all', textFileService, workingCopyService, fileDialogService, editorGroupService);
}
protected doCloseAll(): Promise<any> {
@ -686,9 +692,11 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
id: string,
label: string,
@ITextFileService textFileService: ITextFileService,
@IWorkingCopyService workingCopyService: IWorkingCopyService,
@IFileDialogService fileDialogService: IFileDialogService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(id, label, undefined, textFileService, editorGroupService);
super(id, label, undefined, textFileService, workingCopyService, fileDialogService, editorGroupService);
}
protected async doCloseAll(): Promise<any> {

View file

@ -6,7 +6,7 @@
import 'vs/css!./media/editorgroupview';
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
import { Event, Emitter, Relay } from 'vs/base/common/event';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom';
@ -50,7 +50,8 @@ import { guessMimeTypes } from 'vs/base/common/mime';
import { extname } from 'vs/base/common/resources';
import { Schemas } from 'vs/base/common/network';
import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { ILogService } from 'vs/platform/log/common/log';
export class EditorGroupView extends Themable implements IEditorGroupView {
@ -131,7 +132,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
@IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@IMenuService private readonly menuService: IMenuService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@ILogService private readonly logService: ILogService
) {
super(themeService);
@ -920,64 +923,74 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise<void> {
// Report error only if this was not us restoring previous error state or
// we are told to ignore errors that occur from opening an editor
if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) {
// Report error only if we are not told to ignore errors that occur from opening an editor
if (!isPromiseCanceledError(error) && (!options || !options.ignoreError)) {
// Extract possible error actions from the error
let errorActions: ReadonlyArray<IAction> | undefined = undefined;
if (isErrorWithActions(error)) {
errorActions = (error as IErrorWithActions).actions;
}
// Since it is more likely that errors fail to open when restoring them e.g.
// because files got deleted or moved meanwhile, we do not show any notifications
// if we are still restoring editors.
if (this.isRestored) {
// If the context is USER, we try to show a modal dialog instead of a background notification
if (options?.context === EditorOpenContext.USER) {
const buttons: string[] = [];
if (Array.isArray(errorActions) && errorActions.length > 0) {
errorActions.forEach(action => buttons.push(action.label));
} else {
buttons.push(localize('ok', 'OK'));
// Extract possible error actions from the error
let errorActions: ReadonlyArray<IAction> | undefined = undefined;
if (isErrorWithActions(error)) {
errorActions = (error as IErrorWithActions).actions;
}
let cancelId: number | undefined = undefined;
if (buttons.length === 1) {
buttons.push(localize('cancel', "Cancel"));
cancelId = 1;
// If the context is USER, we try to show a modal dialog instead of a background notification
if (options?.context === EditorOpenContext.USER) {
const buttons: string[] = [];
if (Array.isArray(errorActions) && errorActions.length > 0) {
errorActions.forEach(action => buttons.push(action.label));
} else {
buttons.push(localize('ok', 'OK'));
}
let cancelId: number | undefined = undefined;
if (buttons.length === 1) {
buttons.push(localize('cancel', "Cancel"));
cancelId = 1;
}
const result = await this.dialogService.show(
Severity.Error,
localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()),
buttons,
{
detail: toErrorMessage(error),
cancelId
}
);
// Make sure to run any error action if present
if (result.choice !== cancelId && Array.isArray(errorActions)) {
const errorAction = errorActions[result.choice];
if (errorAction) {
errorAction.run();
}
}
}
const result = await this.dialogService.show(
Severity.Error,
localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()),
buttons,
{
detail: toErrorMessage(error),
cancelId
// Otherwise, show a background notification.
else {
const actions: INotificationActions = { primary: [] };
if (Array.isArray(errorActions)) {
actions.primary = errorActions;
}
);
// Make sure to run any error action if present
if (result.choice !== cancelId && Array.isArray(errorActions)) {
const errorAction = errorActions[result.choice];
if (errorAction) {
errorAction.run();
}
const handle = this.notificationService.notify({
severity: Severity.Error,
message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
actions
});
Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary));
}
}
// Otherwise, show a background notification.
// Restoring: just log errors to console
else {
const actions: INotificationActions = { primary: [] };
if (Array.isArray(errorActions)) {
actions.primary = errorActions;
}
const handle = this.notificationService.notify({
severity: Severity.Error,
message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)),
actions
});
Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary));
this.logService.error(error);
}
}
@ -1260,7 +1273,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Switch to editor that we want to handle and confirm to save/revert
await this.openEditor(editor);
const res = await editor.confirmSave();
const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : editor.getName());
// It could be that the editor saved meanwhile, so we check again
// to see if anything needs to happen before closing for good.

View file

@ -139,7 +139,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
@IThemeService themeService: IThemeService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService storageService: IStorageService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
) {
super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService);

View file

@ -18,7 +18,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor';
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { withNullAsUndefined } from 'vs/base/common/types';
export class EditorPickerEntry extends QuickOpenEntryGroup {
@ -38,8 +37,8 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
};
}
getLabel() {
return withNullAsUndefined(this.editor.getName());
getLabel(): string {
return this.editor.getName();
}
getIcon(): string {

View file

@ -248,7 +248,7 @@ export class NoTabsTitleControl extends TitleControl {
// Editor Label
const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER });
const name = editor.getName() || '';
const name = editor.getName();
const { labelFormat } = this.accessor.partOptions;
let description: string;

View file

@ -65,7 +65,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
}
getTitle(): string | undefined {
getTitle(): string {
if (this.input) {
return this.input.getName();
}
@ -219,7 +219,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
protected getAriaLabel(): string {
let ariaLabel: string;
const inputName = this.input && this.input.getName();
const inputName = this.input?.getName();
if (this.isReadOnly()) {
ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor.");
} else {

View file

@ -287,7 +287,7 @@ export abstract class TitleControl extends Themable {
label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1);
}
applyDragImage(e, withUndefinedAsNull(label), 'monaco-editor-group-drag-image');
applyDragImage(e, label, 'monaco-editor-group-drag-image');
}
}));

View file

@ -661,7 +661,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
// Select element when keys are pressed that signal it
const quickNavKeys = this.quickNavigate.keybindings;
const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
const wasTriggerKeyPressed = quickNavKeys.some(k => {
const [firstPart, chordPart] = k.getParts();
if (chordPart) {
return false;

View file

@ -724,7 +724,7 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup {
export class EditorHistoryEntry extends EditorQuickOpenEntry {
private input: IEditorInput | IResourceInput;
private resource: URI | undefined;
private label: string | undefined;
private label: string;
private description?: string;
private dirty: boolean;
@ -745,7 +745,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
if (input instanceof EditorInput) {
this.resource = resourceForEditorHistory(input, fileService);
this.label = types.withNullAsUndefined(input.getName());
this.label = input.getName();
this.description = input.getDescription();
this.dirty = input.isDirty();
} else {
@ -765,7 +765,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
return this.dirty ? 'dirty' : '';
}
getLabel(): string | undefined {
getLabel(): string {
return this.label;
}

View file

@ -19,6 +19,13 @@
display: flex;
}
.monaco-workbench.windows .part.titlebar,
.monaco-workbench.linux .part.titlebar,
.monaco-workbench.web .part.titlebar {
/* put on own layer due to https://github.com/microsoft/vscode/issues/84806 */
transform: translate3d(0px, 0px, 0px);
}
.monaco-workbench .part.titlebar > .titlebar-drag-region {
top: 0;
left: 0;
@ -68,7 +75,7 @@
position: absolute;
top: 0;
width: 100%;
height: 20%;
height: 4px;
}
.monaco-workbench.windows.fullscreen .part.titlebar > .resizer,

View file

@ -45,8 +45,6 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
// TODO@sbatten https://github.com/microsoft/vscode/issues/81360
// tslint:disable-next-line: import-patterns layering
import { IElectronService } from 'vs/platform/electron/node/electron';
// tslint:disable-next-line: import-patterns layering
import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService';
export class TitlebarPart extends Part implements ITitleService {
@ -107,8 +105,7 @@ export class TitlebarPart extends Part implements ITitleService {
@IContextKeyService contextKeyService: IContextKeyService,
@IHostService private readonly hostService: IHostService,
@IProductService private readonly productService: IProductService,
@optional(IElectronService) private electronService: IElectronService,
@optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService
@optional(IElectronService) private electronService: IElectronService
) {
super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
@ -448,13 +445,8 @@ export class TitlebarPart extends Part implements ITitleService {
// Resizer
this.resizer = append(this.element, $('div.resizer'));
const isMaximized = this.environmentService.configuration.maximized ? true : false;
this.onDidChangeMaximized(isMaximized);
this._register(Event.any(
Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true),
Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false)
)(e => this.onDidChangeMaximized(e)));
this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized)));
this.onDidChangeMaximized(this.layoutService.isWindowMaximized());
}
// Since the title area is used to drag the window, we do not want to steal focus from the

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { runWhenIdle, IdleDeadline } from 'vs/base/common/async';
@ -19,7 +19,7 @@ export namespace Extensions {
export const Workbench = 'workbench.contributions.kind';
}
export type IWorkbenchContributionSignature = IConstructorSignature0<IWorkbenchContribution>;
type IWorkbenchContributionSignature<Service extends BrandedService[]> = new (...services: Service) => IWorkbenchContribution;
export interface IWorkbenchContributionsRegistry {
@ -29,7 +29,7 @@ export interface IWorkbenchContributionsRegistry {
*
* @param phase the lifecycle phase when to instantiate the contribution.
*/
registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void;
registerWorkbenchContribution<Services extends BrandedService[]>(contribution: IWorkbenchContributionSignature<Services>, phase: LifecyclePhase): void;
/**
* Starts the registry by providing the required services.
@ -43,8 +43,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry
private readonly toBeInstantiated: Map<LifecyclePhase, IConstructorSignature0<IWorkbenchContribution>[]> = new Map<LifecyclePhase, IConstructorSignature0<IWorkbenchContribution>[]>();
registerWorkbenchContribution(ctor: IWorkbenchContributionSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void {
registerWorkbenchContribution<Services extends BrandedService[]>(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void {
// Instantiate directly if we are already matching the provided phase
if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) {
this.instantiationService.createInstance(ctor);

View file

@ -294,7 +294,7 @@ export interface IEditorInput extends IDisposable {
/**
* Returns the display name of this input.
*/
getName(): string | undefined;
getName(): string;
/**
* Returns the display description of this input.
@ -360,8 +360,8 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
* Returns the name of this input that can be shown to the user. Examples include showing the name of the input
* above the editor area when the input is shown.
*/
getName(): string | undefined {
return undefined;
getName(): string {
return `Editor ${this.getTypeId()}`;
}
/**
@ -376,7 +376,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
* Returns the title of this input that can be shown to the user. Examples include showing the title of
* the input above the editor area as hover over the input label.
*/
getTitle(verbosity?: Verbosity): string | undefined {
getTitle(verbosity?: Verbosity): string {
return this.getName();
}
@ -415,13 +415,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
return false;
}
/**
* Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result.
*/
confirmSave(): Promise<ConfirmResult> {
return Promise.resolve(ConfirmResult.DONT_SAVE);
}
/**
* Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation.
*/
@ -476,12 +469,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
}
}
export const enum ConfirmResult {
SAVE,
DONT_SAVE,
CANCEL
}
export const enum EncodingMode {
/**
@ -573,10 +560,6 @@ export class SideBySideEditorInput extends EditorInput {
return this.master.isDirty();
}
confirmSave(): Promise<ConfirmResult> {
return this.master.confirmSave();
}
save(): Promise<boolean> {
return this.master.save();
}

View file

@ -7,7 +7,7 @@ import { EditorModel } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { DataUri, basename } from 'vs/base/common/resources';
import { DataUri } from 'vs/base/common/resources';
import { MIME_BINARY } from 'vs/base/common/mime';
/**
@ -20,7 +20,7 @@ export class BinaryEditorModel extends EditorModel {
constructor(
public readonly resource: URI,
private readonly name: string | undefined,
private readonly name: string,
@IFileService private readonly fileService: IFileService
) {
super();
@ -46,7 +46,7 @@ export class BinaryEditorModel extends EditorModel {
* The name of the binary resource.
*/
getName(): string {
return this.name || basename(this.resource);
return this.name;
}
/**

View file

@ -7,7 +7,7 @@ import { EditorInput } from 'vs/workbench/common/editor';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { DataUri } from 'vs/base/common/resources';
import { DataUri, basename } from 'vs/base/common/resources';
/**
* An editor input to present data URIs in a binary editor. Data URIs have the form of:
@ -17,25 +17,31 @@ export class DataUriEditorInput extends EditorInput {
static readonly ID: string = 'workbench.editors.dataUriEditorInput';
private readonly name: string;
private readonly description: string | undefined;
constructor(
private readonly name: string | undefined,
private readonly description: string | undefined,
name: string | undefined,
description: string | undefined,
private readonly resource: URI,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
if (!this.name || !this.description) {
if (!name || !description) {
const metadata = DataUri.parseMetaData(this.resource);
if (!this.name) {
this.name = metadata.get(DataUri.META_DATA_LABEL);
if (!name) {
name = metadata.get(DataUri.META_DATA_LABEL) || basename(resource);
}
if (!this.description) {
this.description = metadata.get(DataUri.META_DATA_DESCRIPTION);
if (!description) {
description = metadata.get(DataUri.META_DATA_DESCRIPTION);
}
}
this.name = name;
this.description = description;
}
getResource(): URI {
@ -46,7 +52,7 @@ export class DataUriEditorInput extends EditorInput {
return DataUriEditorInput.ID;
}
getName(): string | undefined {
getName(): string {
return this.name;
}

View file

@ -18,7 +18,13 @@ export class DiffEditorInput extends SideBySideEditorInput {
private cachedModel: DiffEditorModel | null = null;
constructor(name: string, description: string | undefined, original: EditorInput, modified: EditorInput, private readonly forceOpenAsBinary?: boolean) {
constructor(
name: string,
description: string | undefined,
original: EditorInput,
modified: EditorInput,
private readonly forceOpenAsBinary?: boolean
) {
super(name, description, original, modified);
}

View file

@ -8,7 +8,7 @@ import { suggestFilename } from 'vs/base/common/mime';
import { createMemoizer } from 'vs/base/common/decorators';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
import { basenameOrAuthority, dirname } from 'vs/base/common/resources';
import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor';
import { EditorInput, IEncodingSupport, EncodingMode, Verbosity, IModeSupport } from 'vs/workbench/common/editor';
import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
@ -109,7 +109,7 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup
return this.labelService.getUriLabel(this.resource);
}
getTitle(verbosity: Verbosity): string | undefined {
getTitle(verbosity: Verbosity): string {
if (!this.hasAssociatedFilePath) {
return this.getName();
}
@ -122,8 +122,6 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup
case Verbosity.LONG:
return this.longTitle;
}
return undefined;
}
isDirty(): boolean {
@ -148,10 +146,6 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup
return false;
}
confirmSave(): Promise<ConfirmResult> {
return this.textFileService.confirmSave([this.resource]);
}
save(): Promise<boolean> {
return this.textFileService.save(this.resource);
}

View file

@ -51,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
}
}
getTitle(): string | undefined {
getTitle(): string {
return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer");
}
}

View file

@ -39,7 +39,7 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd';
import { memoize } from 'vs/base/common/decorators';
import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { URI } from 'vs/base/common/uri';
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { isWeb } from 'vs/base/common/platform';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
@ -619,13 +619,13 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGro
return null;
}
getDragLabel?(elements: (OpenEditor | IEditorGroup)[]): string | undefined {
getDragLabel?(elements: (OpenEditor | IEditorGroup)[]): string {
if (elements.length > 1) {
return String(elements.length);
}
const element = elements[0];
return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label;
return element instanceof OpenEditor ? element.editor.getName() : element.label;
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {

View file

@ -41,9 +41,9 @@ export class DirtyFilesIndicator extends Disposable implements IWorkbenchContrib
this.lifecycleService.onShutdown(this.dispose, this);
}
private onWorkingCopyDidChangeDirty(copy: IWorkingCopy): void {
const gotDirty = copy.isDirty();
if (gotDirty && !!(copy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
private onWorkingCopyDidChangeDirty(workingCopy: IWorkingCopy): void {
const gotDirty = workingCopy.isDirty();
if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
return; // do not indicate dirty of working copies that are auto saved after short delay
}

View file

@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { createMemoizer } from 'vs/base/common/decorators';
import { dirname } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor';
import { EncodingMode, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
@ -243,10 +243,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
return model.isDirty();
}
confirmSave(): Promise<ConfirmResult> {
return this.textFileService.confirmSave([this.resource]);
}
save(): Promise<boolean> {
return this.textFileService.save(this.resource);
}

View file

@ -164,7 +164,7 @@ registerEditorAction(class extends EditorAction {
kbOpts: {
weight: KeybindingWeight.EditorContrib,
kbExpr: EditorContextKeys.focus,
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.DownArrow,
primary: undefined,
mac: {
primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.DownArrow,
},
@ -188,7 +188,7 @@ registerEditorAction(class extends EditorAction {
kbOpts: {
weight: KeybindingWeight.EditorContrib,
kbExpr: EditorContextKeys.focus,
primary: KeyMod.Shift | KeyMod.Alt | KeyCode.UpArrow,
primary: undefined,
mac: {
primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.UpArrow,
},

View file

@ -39,7 +39,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
private enableCrashReporter: boolean | undefined;
private treeHorizontalScrolling: boolean | undefined;
private debugConsoleWordWrap: boolean | undefined;
private enableConfigSyncAuth: boolean | undefined;
constructor(
@IHostService private readonly hostService: IHostService,
@ -107,12 +106,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
}
}
// Configuration Sync Auth
if (typeof config.configurationSync?.enableAuth === 'boolean' && config.configurationSync.enableAuth !== this.enableConfigSyncAuth) {
this.enableConfigSyncAuth = config.configurationSync.enableAuth;
changed = true;
}
// Notify only when changed and we are the focused window (avoids notification spam across windows)
if (notify && changed) {
this.doConfirm(

View file

@ -43,8 +43,8 @@ export class PatternInputWidget extends Widget {
private domNode!: HTMLElement;
protected inputBox!: HistoryInputBox;
private _onSubmit = this._register(new Emitter<void>());
onSubmit: CommonEvent<void> = this._onSubmit.event;
private _onSubmit = this._register(new Emitter<boolean>());
onSubmit: CommonEvent<boolean /* triggeredOnType */> = this._onSubmit.event;
private _onCancel = this._register(new Emitter<void>());
onCancel: CommonEvent<void> = this._onCancel.event;
@ -152,7 +152,7 @@ export class PatternInputWidget extends Widget {
this._register(this.inputBox.onDidChange(() => {
if (this.searchConfig.searchOnType) {
this._onCancel.fire();
this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(), this.searchConfig.searchOnTypeDebouncePeriod);
this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), this.searchConfig.searchOnTypeDebouncePeriod);
}
}));
@ -170,7 +170,7 @@ export class PatternInputWidget extends Widget {
private onInputKeyUp(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.Enter:
this._onSubmit.fire();
this._onSubmit.fire(true);
return;
case KeyCode.Escape:
this._onCancel.fire();

View file

@ -41,7 +41,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition
import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler';
import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler';
import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions';
import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand } from 'vs/workbench/contrib/search/browser/searchActions';
import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction } from 'vs/workbench/contrib/search/browser/searchActions';
import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel';
import { SearchView } from 'vs/workbench/contrib/search/browser/searchView';
import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet';
@ -619,6 +619,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({
registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...');
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSearchOnTypeAction, ToggleSearchOnTypeAction.ID, ToggleSearchOnTypeAction.LABEL), 'Search: Toggle Search on Type', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category);
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category);

View file

@ -251,6 +251,30 @@ export class CloseReplaceAction extends Action {
}
}
// --- Toggle Search On Type
export class ToggleSearchOnTypeAction extends Action {
static readonly ID = 'workbench.action.toggleSearchOnType';
static readonly LABEL = nls.localize('toggleTabs', "Toggle Search on Type");
private static readonly searchOnTypeKey = 'search.searchOnType';
constructor(
id: string,
label: string,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(id, label);
}
run(): Promise<any> {
const searchOnType = this.configurationService.getValue<boolean>(ToggleSearchOnTypeAction.searchOnTypeKey);
return this.configurationService.updateValue(ToggleSearchOnTypeAction.searchOnTypeKey, !searchOnType);
}
}
export class RefreshAction extends Action {
static readonly ID: string = 'search.action.refreshSearchResults';
@ -275,7 +299,7 @@ export class RefreshAction extends Action {
run(): Promise<void> {
const searchView = getSearchView(this.viewletService, this.panelService);
if (searchView) {
searchView.onQueryChanged();
searchView.onQueryChanged(false);
}
return Promise.resolve();

View file

@ -268,7 +268,7 @@ export class SearchView extends ViewletPanel {
this.inputPatternIncludes.setValue(patternIncludes);
this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
this.inputPatternIncludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType));
this.inputPatternIncludes.onCancel(() => this.cancelSearch(false));
this.trackInputBox(this.inputPatternIncludes.inputFocusTracker, this.inputPatternIncludesFocused);
@ -284,7 +284,7 @@ export class SearchView extends ViewletPanel {
this.inputPatternExcludes.setValue(patternExclusions);
this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(useExcludesAndIgnoreFiles);
this.inputPatternExcludes.onSubmit(() => this.onQueryChanged(true));
this.inputPatternExcludes.onSubmit(triggeredOnType => this.onQueryChanged(true, triggeredOnType));
this.inputPatternExcludes.onCancel(() => this.cancelSearch(false));
this.inputPatternExcludes.onChangeIgnoreBox(() => this.onQueryChanged(true));
this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused);
@ -386,7 +386,7 @@ export class SearchView extends ViewletPanel {
this.searchWidget.toggleReplace(true);
}
this._register(this.searchWidget.onSearchSubmit(() => this.onQueryChanged()));
this._register(this.searchWidget.onSearchSubmit(triggeredOnType => this.onQueryChanged(false, triggeredOnType)));
this._register(this.searchWidget.onSearchCancel(({ focus }) => this.cancelSearch(focus)));
this._register(this.searchWidget.searchInput.onDidOptionChange(() => this.onQueryChanged(true)));
@ -800,7 +800,7 @@ export class SearchView extends ViewletPanel {
}
this.searchWidget.setValue(selectedText, true);
updatedText = true;
this.onQueryChanged();
this.onQueryChanged(false);
}
}
@ -1155,7 +1155,7 @@ export class SearchView extends ViewletPanel {
this.searchWidget.focus(false);
}
onQueryChanged(preserveFocus?: boolean): void {
onQueryChanged(preserveFocus: boolean, triggeredOnType = false): void {
if (!this.searchWidget.searchInput.inputBox.isInputValid()) {
return;
}
@ -1221,7 +1221,7 @@ export class SearchView extends ViewletPanel {
}
this.validateQuery(query).then(() => {
this.onQueryTriggered(query, options, excludePatternText, includePatternText);
this.onQueryTriggered(query, options, excludePatternText, includePatternText, triggeredOnType);
if (!preserveFocus) {
this.searchWidget.focus(false); // focus back to input field
@ -1251,7 +1251,7 @@ export class SearchView extends ViewletPanel {
});
}
private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): void {
private onQueryTriggered(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): void {
this.addToSearchHistoryDelayer.trigger(() => this.searchWidget.searchInput.onSearchSubmit());
this.inputPatternExcludes.onSearchSubmit();
this.inputPatternIncludes.onSearchSubmit();
@ -1259,13 +1259,13 @@ export class SearchView extends ViewletPanel {
this.viewModel.cancelSearch();
this.currentSearchQ = this.currentSearchQ
.then(() => this.doSearch(query, options, excludePatternText, includePatternText))
.then(() => this.doSearch(query, options, excludePatternText, includePatternText, triggeredOnType))
.then(() => undefined, () => undefined);
}
private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable<void> {
private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable<void> {
let progressComplete: () => void;
this.progressService.withProgress({ location: VIEWLET_ID, delay: 300 }, _progress => {
this.progressService.withProgress({ location: VIEWLET_ID, delay: triggeredOnType ? 300 : 0 }, _progress => {
return new Promise(resolve => progressComplete = resolve);
});
@ -1335,7 +1335,7 @@ export class SearchView extends ViewletPanel {
const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again")));
this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => {
dom.EventHelper.stop(e, false);
this.onQueryChanged();
this.onQueryChanged(false);
}));
} else if (hasIncludes || hasExcludes) {
const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files")));
@ -1345,7 +1345,7 @@ export class SearchView extends ViewletPanel {
this.inputPatternExcludes.setValue('');
this.inputPatternIncludes.setValue('');
this.onQueryChanged();
this.onQueryChanged(false);
}));
} else {
const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings")));

View file

@ -121,8 +121,8 @@ export class SearchWidget extends Widget {
private ignoreGlobalFindBufferOnNextFocus = false;
private previousGlobalFindBufferValue: string | null = null;
private _onSearchSubmit = this._register(new Emitter<void>());
readonly onSearchSubmit: Event<void> = this._onSearchSubmit.event;
private _onSearchSubmit = this._register(new Emitter<boolean>());
readonly onSearchSubmit: Event<boolean /* triggeredOnType */> = this._onSearchSubmit.event;
private _onSearchCancel = this._register(new Emitter<{ focus: boolean }>());
readonly onSearchCancel: Event<{ focus: boolean }> = this._onSearchCancel.event;
@ -454,7 +454,7 @@ export class SearchWidget extends Widget {
this.temporarilySkipSearchOnChange = false;
} else {
this._onSearchCancel.fire({ focus: false });
this._searchDelayer.trigger((() => this.submitSearch()), this.searchConfiguration.searchOnTypeDebouncePeriod);
this._searchDelayer.trigger((() => this.submitSearch(true)), this.searchConfiguration.searchOnTypeDebouncePeriod);
}
}
}
@ -563,7 +563,7 @@ export class SearchWidget extends Widget {
}
}
private submitSearch(): void {
private submitSearch(triggeredOnType = false): void {
this.searchInput.validate();
if (!this.searchInput.inputBox.isInputValid()) {
return;
@ -574,7 +574,7 @@ export class SearchWidget extends Widget {
if (value && useGlobalFindBuffer) {
this.clipboardServce.writeFindText(value);
}
this._onSearchSubmit.fire();
this._onSearchSubmit.fire(triggeredOnType);
}
dispose(): void {

View file

@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, ConfirmResult, IRevertOptions, EditorOptions } from 'vs/workbench/common/editor';
import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, IRevertOptions, EditorOptions } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorModel } from 'vs/platform/editor/common/editor';
import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom';
@ -160,11 +160,6 @@ class TestCustomEditorInput extends EditorInput implements IWorkingCopy {
return this.dirty;
}
confirmSave(): Promise<ConfirmResult> {
// TODO
return Promise.resolve(ConfirmResult.DONT_SAVE);
}
save(): Promise<boolean> {
this.setDirty(false);

View file

@ -25,7 +25,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { isEqual } from 'vs/base/common/resources';
import { IEditorInput } from 'vs/workbench/common/editor';
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
import { timeout } from 'vs/base/common/async';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthTokenStatus.Inactive);
const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`));
@ -33,6 +33,8 @@ const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbenc
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private static readonly ENABLEMENT_SETTING = 'configurationSync.enable';
private readonly syncStatusContext: IContextKey<string>;
private readonly authTokenContext: IContextKey<string>;
private readonly badgeDisposable = this._register(new MutableDisposable());
@ -50,6 +52,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
@ITextFileService private readonly textFileService: ITextFileService,
@IHistoryService private readonly historyService: IHistoryService,
@IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
@IDialogService private readonly dialogService: IDialogService,
) {
super();
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
@ -59,14 +62,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
this.onDidChangeSyncStatus(this.userDataSyncService.status);
this._register(Event.debounce(authTokenService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeAuthTokenStatus(this.authTokenService.status)));
this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status)));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => this.updateBadge()));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING))(() => this.onDidChangeEnablement()));
this.registerActions();
timeout(2000).then(() => {
if (this.authTokenService.status === AuthTokenStatus.Inactive && this.userDataSyncService.status !== SyncStatus.Uninitialized && configurationService.getValue<boolean>('configurationSync.enable')) {
this.showSignInNotification();
}
});
}
private onDidChangeAuthTokenStatus(status: AuthTokenStatus) {
@ -103,14 +100,34 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
private onDidChangeEnablement() {
this.updateBadge();
const enabled = this.configurationService.getValue<boolean>(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING);
if (enabled) {
if (this.authTokenService.status === AuthTokenStatus.Inactive) {
const handle = this.notificationService.prompt(Severity.Info, localize('ask to sign in', "Please sign in with your '{0}' account to sync configuration", "{ACCOUNT_NAME}"),
[
{
label: localize('Sign in', "Sign in"),
run: () => this.signIn()
}
]);
this.signInNotificationDisposable.value = toDisposable(() => handle.close());
handle.onDidClose(() => this.signInNotificationDisposable.clear());
}
} else {
this.signInNotificationDisposable.clear();
}
}
private updateBadge(): void {
this.badgeDisposable.clear();
let badge: IBadge | undefined = undefined;
let clazz: string | undefined;
if (this.authTokenService.status === AuthTokenStatus.Inactive && this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue<boolean>('configurationSync.enable')) {
badge = new NumberBadge(1, () => localize('sign in', "Sign in..."));
if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue<boolean>(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.Inactive) {
badge = new NumberBadge(1, () => localize('sign in', "Sync: Sign in..."));
} else if (this.userDataSyncService.status === SyncStatus.HasConflicts) {
badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts"));
} else if (this.userDataSyncService.status === SyncStatus.Syncing) {
@ -123,16 +140,24 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
}
}
private showSignInNotification(): void {
const handle = this.notificationService.prompt(Severity.Info, localize('show sign in', "Please sign in to Settings Sync service to start syncing configuration."),
[
{
label: localize('sign in', "Sign in..."),
run: () => this.signIn()
}
]);
this.signInNotificationDisposable.value = toDisposable(() => handle.close());
handle.onDidClose(() => this.signInNotificationDisposable.clear());
private async turnOn(): Promise<void> {
if (this.authTokenService.status === AuthTokenStatus.Inactive) {
const result = await this.dialogService.confirm({
type: 'info',
message: localize('sign in to account', "Sign in to {0}", "{ACCOUNT_NAME}"),
detail: localize('ask to sign in', "Please sign in with your '{0}' account to sync configuration", "{ACCOUNT_NAME}"),
primaryButton: localize('Sign in', "Sign in")
});
if (!result.confirmed) {
return;
}
await this.signIn();
}
await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true);
}
private async turnOff(): Promise<void> {
await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, false);
}
private async signIn(): Promise<void> {
@ -194,17 +219,93 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
private registerActions(): void {
const signInMenuItem: IMenuItem = {
const startSyncMenuItem: IMenuItem = {
group: '5_sync',
command: {
id: 'workbench.userData.actions.login',
title: localize('sign in', "Sign in...")
id: 'workbench.userData.actions.syncStart',
title: localize('start sync', "Sync: Turn On")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Inactive), ContextKeyExpr.has('config.configurationSync.enable')),
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)),
};
CommandsRegistry.registerCommand(signInMenuItem.command.id, () => this.signIn());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, signInMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signInMenuItem);
CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.turnOn());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem);
const signInCommandId = 'workbench.userData.actions.login';
const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Inactive));
CommandsRegistry.registerCommand(signInCommandId, () => this.signIn());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
command: {
id: signInCommandId,
title: localize('global activity sign in', "Sync: Sign in... (1)")
},
when: signInWhenContext,
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: signInCommandId,
title: localize('sign in', "Sync: Sign in...")
},
when: signInWhenContext,
});
const stopSycCommand = {
id: 'workbench.userData.actions.stopSync',
title: localize('stop sync', "Sync: Turn Off")
};
CommandsRegistry.registerCommand(stopSycCommand.id, () => this.turnOff());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
command: stopSycCommand,
when: ContextKeyExpr.and(ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.HasConflicts))
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: stopSycCommand,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)),
});
const resolveConflictsCommandId = 'workbench.userData.actions.resolveConflicts';
const resolveConflictsWhenContext = CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts);
CommandsRegistry.registerCommand(resolveConflictsCommandId, () => this.handleConflicts());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
group: '5_sync',
command: {
id: resolveConflictsCommandId,
title: localize('resolveConflicts_global', "Sync: Resolve Conflicts (1)"),
},
when: resolveConflictsWhenContext,
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: resolveConflictsCommandId,
title: localize('resolveConflicts', "Sync: Resolve Conflicts"),
},
when: resolveConflictsWhenContext,
});
const continueSyncCommandId = 'workbench.userData.actions.continueSync';
CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Sync: Continue"),
iconLocation: {
light: SYNC_PUSH_LIGHT_ICON_URI,
dark: SYNC_PUSH_DARK_ICON_URI
}
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())),
});
const signOutMenuItem: IMenuItem = {
group: '5_sync',
@ -215,66 +316,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active)),
};
CommandsRegistry.registerCommand(signOutMenuItem.command.id, () => this.signOut());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, signOutMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, signOutMenuItem);
const startSyncMenuItem: IMenuItem = {
group: '5_sync',
command: {
id: 'workbench.userData.actions.syncStart',
title: localize('start sync', "Configuration Sync: Turn On")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.configurationSync.enable')),
};
CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', true));
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem);
const stopSyncMenuItem: IMenuItem = {
group: '5_sync',
command: {
id: 'workbench.userData.actions.stopSync',
title: localize('stop sync', "Configuration Sync: Turn Off")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.configurationSync.enable')),
};
CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', false));
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, stopSyncMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, stopSyncMenuItem);
const resolveConflictsMenuItem: IMenuItem = {
group: '5_sync',
command: {
id: 'sync.resolveConflicts',
title: localize('resolveConflicts', "Configuration Sync: Resolve Conflicts"),
},
when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts),
};
CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, () => this.handleConflicts());
MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem);
const continueSyncCommandId = 'workbench.userData.actions.continueSync';
CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync());
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Configuration Sync: Continue")
},
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)),
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: continueSyncCommandId,
title: localize('continue sync', "Configuration Sync: Continue"),
iconLocation: {
light: SYNC_PUSH_LIGHT_ICON_URI,
dark: SYNC_PUSH_DARK_ICON_URI
}
},
group: 'navigation',
order: 1,
when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())),
});
}
}

View file

@ -101,7 +101,7 @@ export class WebviewInput extends EditorInput {
return this._name;
}
public getTitle(_verbosity?: Verbosity) {
public getTitle(_verbosity?: Verbosity): string {
return this.getName();
}

View file

@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron';
import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@ -60,7 +60,9 @@ import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/pl
import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService';
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { Event } from 'vs/base/common/event';
export class ElectronWindow extends Disposable {
@ -103,7 +105,8 @@ export class ElectronWindow extends Disposable {
@ITunnelService private readonly tunnelService: ITunnelService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService,
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService
@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService
) {
super();
@ -124,7 +127,7 @@ export class ElectronWindow extends Disposable {
});
// Support runAction event
ipc.on('vscode:runAction', async (event: Event, request: IRunActionInWindowRequest) => {
ipc.on('vscode:runAction', async (event: IpcEvent, request: IRunActionInWindowRequest) => {
const args: unknown[] = request.args || [];
// If we run an action from the touchbar, we fill in the currently active resource
@ -155,27 +158,27 @@ export class ElectronWindow extends Disposable {
});
// Support runKeybinding event
ipc.on('vscode:runKeybinding', (event: Event, request: IRunKeybindingInWindowRequest) => {
ipc.on('vscode:runKeybinding', (event: IpcEvent, request: IRunKeybindingInWindowRequest) => {
if (document.activeElement) {
this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement);
}
});
// Error reporting from main
ipc.on('vscode:reportError', (event: Event, error: string) => {
ipc.on('vscode:reportError', (event: IpcEvent, error: string) => {
if (error) {
errors.onUnexpectedError(JSON.parse(error));
}
});
// Support openFiles event for existing and new files
ipc.on('vscode:openFiles', (event: Event, request: IOpenFileRequest) => this.onOpenFiles(request));
ipc.on('vscode:openFiles', (event: IpcEvent, request: IOpenFileRequest) => this.onOpenFiles(request));
// Support addFolders event if we have a workspace opened
ipc.on('vscode:addFolders', (event: Event, request: IAddFoldersRequest) => this.onAddFoldersRequest(request));
ipc.on('vscode:addFolders', (event: IpcEvent, request: IAddFoldersRequest) => this.onAddFoldersRequest(request));
// Message support
ipc.on('vscode:showInfoMessage', (event: Event, message: string) => {
ipc.on('vscode:showInfoMessage', (event: IpcEvent, message: string) => {
this.notificationService.info(message);
});
@ -213,7 +216,7 @@ export class ElectronWindow extends Disposable {
});
// keyboard layout changed event
ipc.on('vscode:accessibilitySupportChanged', (event: Event, accessibilitySupportEnabled: boolean) => {
ipc.on('vscode:accessibilitySupportChanged', (event: IpcEvent, accessibilitySupportEnabled: boolean) => {
this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled);
});
@ -267,6 +270,10 @@ export class ElectronWindow extends Disposable {
if (isMacintosh) {
this._register(this.workingCopyService.onDidChangeDirty(workingCopy => {
const gotDirty = workingCopy.isDirty();
if (gotDirty && !!(workingCopy.capabilities & WorkingCopyCapabilities.AutoSave) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
return; // do not indicate dirty of working copies that are auto saved after short delay
}
if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) {
const hasDirtyFiles = this.workingCopyService.hasDirty;
this.isDocumentedEdited = hasDirtyFiles;
@ -275,6 +282,17 @@ export class ElectronWindow extends Disposable {
}
}));
}
this._register(Event.any(
Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true),
Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false)
)(e => this.onDidChangeMaximized(e)));
this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false);
}
private onDidChangeMaximized(maximized: boolean): void {
this.layoutService.updateWindowMaximizedState(maximized);
}
private onDidVisibleEditorsChange(): void {

View file

@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -20,8 +20,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IFileService } from 'vs/platform/files/common/files';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import Severity from 'vs/base/common/severity';
export abstract class AbstractFileDialogService {
export abstract class AbstractFileDialogService implements IFileDialogService {
_serviceBrand: undefined;
@ -34,6 +35,7 @@ export abstract class AbstractFileDialogService {
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IFileService protected readonly fileService: IFileService,
@IOpenerService protected readonly openerService: IOpenerService,
@IDialogService private readonly dialogService: IDialogService
) { }
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
@ -78,6 +80,40 @@ export abstract class AbstractFileDialogService {
return this.defaultFilePath(schemeFilter);
}
async showSaveConfirm(fileNameOrResources: string | URI[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
}
if (Array.isArray(fileNameOrResources) && fileNameOrResources.length === 0) {
return ConfirmResult.DONT_SAVE;
}
let message: string;
if (typeof fileNameOrResources === 'string' || fileNameOrResources.length === 1) {
message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNameOrResources === 'string' ? fileNameOrResources : resources.basename(fileNameOrResources[0]));
} else {
message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNameOrResources.length), fileNameOrResources);
}
const buttons: string[] = [
Array.isArray(fileNameOrResources) && fileNameOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"),
nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"),
nls.localize('cancel', "Cancel")
];
const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, {
cancelId: 2,
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.")
});
switch (choice) {
case 0: return ConfirmResult.SAVE;
case 1: return ConfirmResult.DONT_SAVE;
default: return ConfirmResult.CANCEL;
}
}
protected abstract addFileSchemaIfNeeded(schema: string): string[];
protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise<any> {
@ -179,4 +215,12 @@ export abstract class AbstractFileDialogService {
protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string {
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
}
abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise<void>;
abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void>;
abstract pickFileToSave(options: ISaveDialogOptions): Promise<URI | undefined>;
abstract showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
abstract showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
}

View file

@ -6,7 +6,7 @@
import { SaveDialogOptions, OpenDialogOptions } from 'electron';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -33,8 +33,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService,
@IOpenerService openerService: IOpenerService,
@IElectronService private readonly electronService: IElectronService
) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); }
@IElectronService private readonly electronService: IElectronService,
@IDialogService dialogService: IDialogService
) {
super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService);
}
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {
return {
@ -180,6 +183,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
// Don't allow untitled schema through.
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
}
async showSaveConfirm(fileNameOrResources: string | URI[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
if (!this.environmentService.args['extension-development-confirm-save']) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
}
}
return super.showSaveConfirm(fileNameOrResources);
}
}
registerSingleton(IFileDialogService, FileDialogService, true);

View file

@ -623,7 +623,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Data URI
else if (resource.scheme === Schemas.data) {
input = instantiationService.createInstance(DataUriEditorInput, label, description, resource);
input = instantiationService.createInstance(DataUriEditorInput, label || basename(resource), description, resource);
}
// Resource

View file

@ -484,8 +484,8 @@ export class HistoryService extends Disposable implements IHistoryService {
private handleEditorEventInHistory(editor?: IBaseEditor): void {
const input = editor?.input;
// Ensure we have at least a name to show and not configured to exclude input
if (!input || !input.getName() || !this.include(input)) {
// Ensure we have not configured to exclude input
if (!input || !this.include(input)) {
return;
}

View file

@ -41,6 +41,11 @@ export interface IWorkbenchLayoutService extends ILayoutService {
*/
readonly onFullscreenChange: Event<boolean>;
/**
* Emits when the window is maximized or unmaximized.
*/
readonly onMaximizeChange: Event<boolean>;
/**
* Emits when centered layout is enabled or disabled.
*/
@ -188,4 +193,15 @@ export interface IWorkbenchLayoutService extends ILayoutService {
* Register a part to participate in the layout.
*/
registerPart(part: Part): void;
/**
* Returns whether the window is maximized.
*/
isWindowMaximized(): boolean;
/**
* Updates the maximized state of the window.
*/
updateWindowMaximizedState(maximized: boolean): void;
}

View file

@ -21,7 +21,7 @@ export class PreferencesEditorInput extends SideBySideEditorInput {
return PreferencesEditorInput.ID;
}
getTitle(verbosity: Verbosity): string | undefined {
getTitle(verbosity: Verbosity): string {
return this.master.getTitle(verbosity);
}
}

View file

@ -9,7 +9,7 @@ import { Emitter, AsyncEmitter } from 'vs/base/common/event';
import * as platform from 'vs/base/common/platform';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
import { IRevertOptions } from 'vs/workbench/common/editor';
import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files';
@ -24,9 +24,9 @@ import { Schemas } from 'vs/base/common/network';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources';
import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { coalesce } from 'vs/base/common/arrays';
@ -232,7 +232,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
private async confirmBeforeShutdown(): Promise<boolean> {
const confirm = await this.confirmSave();
const confirm = await this.fileDialogService.showSaveConfirm(this.getDirty());
// Save
if (confirm === ConfirmResult.SAVE) {
@ -496,30 +496,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
return result.results.length === 1 && !!result.results[0].success;
}
async confirmSave(resources?: URI[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
}
const resourcesToConfirm = this.getDirty(resources);
if (resourcesToConfirm.length === 0) {
return ConfirmResult.DONT_SAVE;
}
return promptSave(this.dialogService, resourcesToConfirm);
}
async confirmOverwrite(resource: URI): Promise<boolean> {
const confirm: IConfirmation = {
message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)),
detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
return (await this.dialogService.confirm(confirm)).confirmed;
}
saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise<ITextFileOperationResult>;
saveAll(resources: URI[], options?: ISaveOptions): Promise<ITextFileOperationResult>;
saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise<ITextFileOperationResult> {
@ -830,6 +806,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
}
private async confirmOverwrite(resource: URI): Promise<boolean> {
const confirm: IConfirmation = {
message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)),
detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))),
primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
type: 'warning'
};
return (await this.dialogService.confirm(confirm)).confirmed;
}
private suggestFileName(untitledResource: URI): URI {
const untitledFileName = this.untitledTextEditorService.suggestFileName(untitledResource);
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
@ -939,26 +926,3 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
super.dispose();
}
}
export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) {
const message = resourcesToConfirm.length === 1
? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0]))
: getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm);
const buttons: string[] = [
resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"),
nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"),
nls.localize('cancel', "Cancel")
];
const { choice } = await dialogService.show(Severity.Warning, message, buttons, {
cancelId: 2,
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.")
});
switch (choice) {
case 0: return ConfirmResult.SAVE;
case 1: return ConfirmResult.DONT_SAVE;
default: return ConfirmResult.CANCEL;
}
}

View file

@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { Event, IWaitUntil } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor';
import { IEncodingSupport, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor';
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
@ -129,17 +129,8 @@ export interface ITextFileService extends IDisposable {
* Move a file. If the file is dirty, its contents will be preserved and restored.
*/
move(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>;
/**
* Brings up the confirm dialog to either save, don't save or cancel.
*
* @param resources the resources of the files to ask for confirmation or null if
* confirming for all dirty resources.
*/
confirmSave(resources?: URI[]): Promise<ConfirmResult>;
}
export class FileOperationWillRunEvent implements IWaitUntil {
constructor(
@ -157,7 +148,6 @@ export class FileOperationWillRunEvent implements IWaitUntil {
}
}
export class FileOperationDidRunEvent {
constructor(
@ -167,7 +157,6 @@ export class FileOperationDidRunEvent {
) { }
}
export interface IReadTextFileOptions extends IReadFileOptions {
/**

View file

@ -39,7 +39,6 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ConfirmResult } from 'vs/workbench/common/editor';
import { assign } from 'vs/base/common/objects';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
@ -287,7 +286,8 @@ export class NativeTextFileService extends AbstractTextFileService {
private async sudoPromptCopy(source: string, target: string, options?: IWriteTextFileOptions): Promise<void> {
// load sudo-prompt module lazy
const sudoPrompt = await import('sudo-prompt');
// @ts-ignore TODO@ben wait for update of sudo-prompt
const sudoPrompt: { exec(cmd: string, options: { name?: string, icns?: string }, callback: (error: string, stdout: string, stderr: string) => void): void } = await import('sudo-prompt');
return new Promise<void>((resolve, reject) => {
const promptOptions = {
@ -315,16 +315,6 @@ export class NativeTextFileService extends AbstractTextFileService {
protected getWindowCount(): Promise<number> {
return this.electronService.getWindowCount();
}
async confirmSave(resources?: URI[]): Promise<ConfirmResult> {
if (this.environmentService.isExtensionDevelopment) {
if (!this.environmentService.args['extension-development-confirm-save']) {
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
}
}
return super.confirmSave(resources);
}
}
export interface IEncodingOverride {

View file

@ -7,12 +7,11 @@ import * as sinon from 'sinon';
import * as platform from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ConfirmResult } from 'vs/workbench/common/editor';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
@ -22,6 +21,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
import { Schemas } from 'vs/base/common/network';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
class ServiceAccessor {
constructor(
@ -32,7 +32,8 @@ class ServiceAccessor {
@IWorkspaceContextService public contextService: TestContextService,
@IModelService public modelService: ModelServiceImpl,
@IFileService public fileService: TestFileService,
@IElectronService public electronService: TestElectronService
@IElectronService public electronService: TestElectronService,
@IFileDialogService public fileDialogService: TestFileDialogService
) {
}
}
@ -87,7 +88,7 @@ suite('Files - TextFileService', () => {
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.CANCEL);
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
await model.load();
model.textEditorModel!.setValue('foo');
@ -103,7 +104,7 @@ suite('Files - TextFileService', () => {
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.DONT_SAVE);
accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE);
accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } });
await model.load();
@ -129,7 +130,7 @@ suite('Files - TextFileService', () => {
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.resource, model);
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.SAVE);
accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE);
accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } });
await model.load();
@ -429,7 +430,7 @@ suite('Files - TextFileService', () => {
accessor.electronService.windowCount = Promise.resolve(2);
}
// Set cancel to force a veto if hot exit does not trigger
service.setConfirmResult(ConfirmResult.CANCEL);
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
await model.load();
model.textEditorModel!.setValue('foo');

View file

@ -50,6 +50,8 @@ export interface IWorkingCopyService {
isDirty(resource: URI): boolean;
getDirty(...resources: URI[]): IWorkingCopy[];
//#endregion
@ -69,6 +71,34 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic
private readonly _onDidChangeDirty = this._register(new Emitter<IWorkingCopy>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
getDirty(...resources: URI[]): IWorkingCopy[] {
const dirtyWorkingCopies: IWorkingCopy[] = [];
// Specific resource(s)
if (resources.length > 0) {
for (const resource of resources) {
this.fillDirty(this.mapResourceToWorkingCopy.get(resource.toString()), dirtyWorkingCopies);
}
}
// All resources
else {
this.fillDirty(this.workingCopies, dirtyWorkingCopies);
}
return dirtyWorkingCopies;
}
private fillDirty(workingCopies: Set<IWorkingCopy> | undefined, target: IWorkingCopy[]): void {
if (workingCopies) {
for (const workingCopy of workingCopies) {
if (workingCopy.isDirty()) {
target.push(workingCopy);
}
}
}
}
isDirty(resource: URI): boolean {
const workingCopies = this.mapResourceToWorkingCopy.get(resource.toString());
if (workingCopies) {

View file

@ -56,6 +56,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.hasDirty, false);
assert.equal(service.dirtyCount, 0);
assert.equal(service.getDirty().length, 0);
assert.equal(service.getDirty(URI.file('/'), URI.file('/some')).length, 0);
assert.equal(service.isDirty(URI.file('/')), false);
// resource 1
@ -66,6 +68,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.dirtyCount, 0);
assert.equal(service.isDirty(resource1), false);
assert.equal(service.hasDirty, false);
assert.equal(service.getDirty(resource1).length, 0);
assert.equal(service.getDirty().length, 0);
copy1.setDirty(true);
@ -74,6 +78,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.hasDirty, true);
assert.equal(onDidChangeDirty.length, 1);
assert.equal(onDidChangeDirty[0], copy1);
assert.equal(service.getDirty(resource1).length, 1);
assert.equal(service.getDirty().length, 1);
copy1.setDirty(false);
@ -82,6 +88,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.hasDirty, false);
assert.equal(onDidChangeDirty.length, 2);
assert.equal(onDidChangeDirty[1], copy1);
assert.equal(service.getDirty(resource1).length, 0);
assert.equal(service.getDirty().length, 0);
unregister1.dispose();
@ -93,6 +101,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.dirtyCount, 1);
assert.equal(service.isDirty(resource2), true);
assert.equal(service.hasDirty, true);
assert.equal(service.getDirty(resource1, resource2).length, 1);
assert.equal(service.getDirty().length, 1);
assert.equal(onDidChangeDirty.length, 3);
assert.equal(onDidChangeDirty[2], copy2);
@ -102,6 +112,8 @@ suite('WorkingCopyService', () => {
assert.equal(service.hasDirty, false);
assert.equal(onDidChangeDirty.length, 4);
assert.equal(onDidChangeDirty[3], copy2);
assert.equal(service.getDirty(resource1, resource2).length, 0);
assert.equal(service.getDirty().length, 0);
});
test('registry - multiple copies on same resource', () => {
@ -123,23 +135,31 @@ suite('WorkingCopyService', () => {
assert.equal(service.dirtyCount, 1);
assert.equal(onDidChangeDirty.length, 1);
assert.equal(service.isDirty(resource), true);
assert.equal(service.getDirty(resource).length, 1);
assert.equal(service.getDirty().length, 1);
copy2.setDirty(true);
assert.equal(service.dirtyCount, 2);
assert.equal(onDidChangeDirty.length, 2);
assert.equal(service.isDirty(resource), true);
assert.equal(service.getDirty(resource).length, 2);
assert.equal(service.getDirty().length, 2);
unregister1.dispose();
assert.equal(service.dirtyCount, 1);
assert.equal(onDidChangeDirty.length, 3);
assert.equal(service.isDirty(resource), true);
assert.equal(service.getDirty(resource).length, 1);
assert.equal(service.getDirty().length, 1);
unregister2.dispose();
assert.equal(service.dirtyCount, 0);
assert.equal(onDidChangeDirty.length, 4);
assert.equal(service.isDirty(resource), false);
assert.equal(service.getDirty(resource).length, 0);
assert.equal(service.getDirty().length, 0);
});
});

View file

@ -31,4 +31,4 @@ suite('DataUriEditorInput', () => {
assert.equal(model.getMime(), 'image/png');
});
});
});
});

View file

@ -22,7 +22,7 @@ suite('Workbench editor input', () => {
assert(input.matches(input));
assert(!input.matches(otherInput));
assert(!input.matches(null));
assert(!input.getName());
assert(input.getName());
input.onDispose(() => {
assert(true);
@ -84,4 +84,4 @@ suite('Workbench editor input', () => {
otherInput.dispose();
assert.equal(counter, 2);
});
});
});

View file

@ -527,6 +527,25 @@ suite('ExtHostTypes', function () {
string.appendVariable('BAR', b => { });
assert.equal(string.value, '${BAR}');
string = new types.SnippetString();
string.appendChoice(['b', 'a', 'r']);
assert.equal(string.value, '${1|b,a,r|}');
string = new types.SnippetString();
string.appendChoice(['b', 'a', 'r'], 0);
assert.equal(string.value, '${0|b,a,r|}');
string = new types.SnippetString();
string.appendText('foo').appendChoice(['far', 'boo']).appendText('bar');
assert.equal(string.value, 'foo${1|far,boo|}bar');
string = new types.SnippetString();
string.appendText('foo').appendChoice(['far', '$boo']).appendText('bar');
assert.equal(string.value, 'foo${1|far,\\$boo|}bar');
string = new types.SnippetString();
string.appendText('foo').appendPlaceholder('farboo').appendChoice(['far', 'boo']).appendText('bar');
assert.equal(string.value, 'foo${1:farboo}${2|far,boo|}bar');
});
test('instanceof doesn\'t work for FileSystemError #49386', function () {

View file

@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor';
import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor';
import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
import { Event, Emitter } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
@ -49,7 +49,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { Range } from 'vs/editor/common/core/range';
import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs';
import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -192,7 +192,6 @@ export class TestTextFileService extends NativeTextFileService {
public cleanupBackupsBeforeShutdownCalled!: boolean;
private promptPath!: URI;
private confirmResult!: ConfirmResult;
private resolveTextContentError!: FileOperationError | null;
constructor(
@ -241,10 +240,6 @@ export class TestTextFileService extends NativeTextFileService {
this.promptPath = path;
}
public setConfirmResult(result: ConfirmResult): void {
this.confirmResult = result;
}
public setResolveTextContentErrorOnce(error: FileOperationError): void {
this.resolveTextContentError = error;
}
@ -275,14 +270,6 @@ export class TestTextFileService extends NativeTextFileService {
return Promise.resolve(this.promptPath);
}
public confirmSave(_resources?: URI[]): Promise<ConfirmResult> {
return Promise.resolve(this.confirmResult);
}
public confirmOverwrite(_resource: URI): Promise<boolean> {
return Promise.resolve(true);
}
protected cleanupBackupsBeforeShutdown(): Promise<void> {
this.cleanupBackupsBeforeShutdownCalled = true;
return Promise.resolve();
@ -303,6 +290,8 @@ export function workbenchInstantiationService(): IInstantiationService {
instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService));
instantiationService.stub(IStorageService, new TestStorageService());
instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService());
instantiationService.stub(IDialogService, new TestDialogService());
instantiationService.stub(IFileDialogService, new TestFileDialogService());
instantiationService.stub(IElectronService, new TestElectronService());
instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl));
instantiationService.stub(IHistoryService, new TestHistoryService());
@ -420,6 +409,8 @@ export class TestFileDialogService implements IFileDialogService {
public _serviceBrand: undefined;
private confirmResult!: ConfirmResult;
public defaultFilePath(_schemeFilter?: string): URI | undefined {
return undefined;
}
@ -450,6 +441,12 @@ export class TestFileDialogService implements IFileDialogService {
public showOpenDialog(_options: IOpenDialogOptions): Promise<URI[] | undefined> {
return Promise.resolve(undefined);
}
public setConfirmResult(result: ConfirmResult): void {
this.confirmResult = result;
}
public showSaveConfirm(resources: string | URI[]): Promise<ConfirmResult> {
return Promise.resolve(this.confirmResult);
}
}
export class TestLayoutService implements IWorkbenchLayoutService {
@ -463,6 +460,7 @@ export class TestLayoutService implements IWorkbenchLayoutService {
onZenModeChange: Event<boolean> = Event.None;
onCenteredLayoutChange: Event<boolean> = Event.None;
onFullscreenChange: Event<boolean> = Event.None;
onMaximizeChange: Event<boolean> = Event.None;
onPanelPositionChange: Event<string> = Event.None;
onPartVisibilityChange: Event<void> = Event.None;
onLayout = Event.None;
@ -572,6 +570,12 @@ export class TestLayoutService implements IWorkbenchLayoutService {
public resizePart(_part: Parts, _sizeChange: number): void { }
public registerPart(part: Part): void { }
isWindowMaximized() {
return false;
}
public updateWindowMaximizedState(maximized: boolean): void { }
}
let activeViewlet: Viewlet = {} as any;

View file

@ -6068,10 +6068,10 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
onigasm-umd@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257"
integrity sha512-v2eMOJu7iE444L2iJN+U6s6s5S0y7oj/N0DAkrd6wokRtTVoq/v/yaDI1lIqFrTeJbNtqNzYvguDF5yNzW3Rvw==
onigasm-umd@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.4.tgz#27ee87f7496c66ad40cebfbc0d418c19bb7db5ec"
integrity sha512-N9VqCUhl9KBuzm47vcK8T/xUnbYylIhMN45Rwltlo1sZc3QUDda6SxIlyVB8r0SJQwURv8JOHjyXjjCriGvzRg==
oniguruma@^7.2.0:
version "7.2.0"
@ -8150,10 +8150,10 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
sudo-prompt@9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a"
integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ==
sudo-prompt@9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.0.tgz#9618823e748ce19e2d9e481feaf3ada7d52df52f"
integrity sha512-bJigY3ELFd2ZA7gfyQ4wMZIp1EICPFQcMe3RgSz5OQTzrPPaeryhgaxbInO/G62vpiqJs37qlGdb9TaqHeF2yA==
supports-color@1.2.0:
version "1.2.0"