Merge remote-tracking branch 'origin/main' into alex/main-process-extension-host

This commit is contained in:
Alex Dima
2021-10-25 15:55:19 +02:00
41 changed files with 470 additions and 308 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
{
"kind": 2,
"language": "github-issues",
"value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry\n\n// current milestone name\n$milestone=milestone:\"October 2021\""
"value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub\n\n// current milestone name\n$milestone=milestone:\"October 2021\""
},
{
"kind": 1,
@@ -83,7 +83,7 @@ export class SettingsDocument {
completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)")));
completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code")));
completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH")));
completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "a dirty indicator if the active editor is dirty")));
completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "an indicator for when the active editor has unsaved changes")));
completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values")));
return Promise.resolve(completions);
@@ -25,6 +25,8 @@
{ "open": "(", "close": ")" },
{ "open": "<", "close": ">" }
],
"colorizedBracketPairs": [
],
"folding": {
"markers": {
"start": "^\\s*<!--\\s*#region\\b.*-->",
+1 -1
View File
@@ -29,6 +29,6 @@ if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then
fi
# Create $XDG_RUNTIME_DIR if not exists (to be removed when https://pad.lv/1656340 is fixed)
[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p "$XDG_RUNTIME_DIR" -m 700
[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p -m 700 "$XDG_RUNTIME_DIR"
exec "$@"
+19 -13
View File
@@ -741,13 +741,17 @@ interface ResourceMapKeyFn {
(resource: URI): string;
}
class ResourceMapEntry<T> {
constructor(readonly uri: URI, readonly value: T) { }
}
export class ResourceMap<T> implements Map<URI, T> {
private static readonly defaultToKey = (resource: URI) => resource.toString();
readonly [Symbol.toStringTag] = 'ResourceMap';
private readonly map: Map<string, T>;
private readonly map: Map<string, ResourceMapEntry<T>>;
private readonly toKey: ResourceMapKeyFn;
/**
@@ -774,12 +778,12 @@ export class ResourceMap<T> implements Map<URI, T> {
}
set(resource: URI, value: T): this {
this.map.set(this.toKey(resource), value);
this.map.set(this.toKey(resource), new ResourceMapEntry(resource, value));
return this;
}
get(resource: URI): T | undefined {
return this.map.get(this.toKey(resource));
return this.map.get(this.toKey(resource))?.value;
}
has(resource: URI): boolean {
@@ -802,30 +806,32 @@ export class ResourceMap<T> implements Map<URI, T> {
if (typeof thisArg !== 'undefined') {
clb = clb.bind(thisArg);
}
for (let [index, value] of this.map) {
clb(value, URI.parse(index), <any>this);
for (let [_, entry] of this.map) {
clb(entry.value, entry.uri, <any>this);
}
}
values(): IterableIterator<T> {
return this.map.values();
*values(): IterableIterator<T> {
for (let entry of this.map.values()) {
yield entry.value;
}
}
*keys(): IterableIterator<URI> {
for (let key of this.map.keys()) {
yield URI.parse(key);
for (let entry of this.map.values()) {
yield entry.uri;
}
}
*entries(): IterableIterator<[URI, T]> {
for (let tuple of this.map.entries()) {
yield [URI.parse(tuple[0]), tuple[1]];
for (let entry of this.map.values()) {
yield [entry.uri, entry.value];
}
}
*[Symbol.iterator](): IterableIterator<[URI, T]> {
for (let item of this.map) {
yield [URI.parse(item[0]), item[1]];
for (let [, entry] of this.map) {
yield [entry.uri, entry.value];
}
}
}
@@ -13,6 +13,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { Promises } from 'vs/base/node/pfs';
import { isStorageItemsChangeEvent, IStorageDatabase, IStorageItemsChangeEvent, Storage } from 'vs/base/parts/storage/common/storage';
import { ISQLiteStorageDatabaseOptions, SQLiteStorageDatabase } from 'vs/base/parts/storage/node/storage';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
flakySuite('Storage Library', function () {
@@ -29,139 +30,142 @@ flakySuite('Storage Library', function () {
return Promises.rm(testDir);
});
test('basics', async () => {
const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
test('basics', () => {
return runWithFakedTimers({}, async function () {
const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
await storage.init();
await storage.init();
// Empty fallbacks
strictEqual(storage.get('foo', 'bar'), 'bar');
strictEqual(storage.getNumber('foo', 55), 55);
strictEqual(storage.getBoolean('foo', true), true);
// Empty fallbacks
strictEqual(storage.get('foo', 'bar'), 'bar');
strictEqual(storage.getNumber('foo', 55), 55);
strictEqual(storage.getBoolean('foo', true), true);
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
});
await storage.whenFlushed(); // returns immediately when no pending updates
// Simple updates
const set1Promise = storage.set('bar', 'foo');
const set2Promise = storage.set('barNumber', 55);
const set3Promise = storage.set('barBoolean', true);
let flushPromiseResolved = false;
storage.whenFlushed().then(() => flushPromiseResolved = true);
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.getNumber('barNumber'), 55);
strictEqual(storage.getBoolean('barBoolean'), true);
strictEqual(changes.size, 3);
ok(changes.has('bar'));
ok(changes.has('barNumber'));
ok(changes.has('barBoolean'));
let setPromiseResolved = false;
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
strictEqual(setPromiseResolved, true);
strictEqual(flushPromiseResolved, true);
changes = new Set<string>();
// Does not trigger events for same update values
storage.set('bar', 'foo');
storage.set('barNumber', 55);
storage.set('barBoolean', true);
strictEqual(changes.size, 0);
// Simple deletes
const delete1Promise = storage.delete('bar');
const delete2Promise = storage.delete('barNumber');
const delete3Promise = storage.delete('barBoolean');
ok(!storage.get('bar'));
ok(!storage.getNumber('barNumber'));
ok(!storage.getBoolean('barBoolean'));
strictEqual(changes.size, 3);
ok(changes.has('bar'));
ok(changes.has('barNumber'));
ok(changes.has('barBoolean'));
changes = new Set<string>();
// Does not trigger events for same delete values
storage.delete('bar');
storage.delete('barNumber');
storage.delete('barBoolean');
strictEqual(changes.size, 0);
let deletePromiseResolved = false;
await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true);
strictEqual(deletePromiseResolved, true);
await storage.close();
await storage.close(); // it is ok to call this multiple times
});
await storage.whenFlushed(); // returns immediately when no pending updates
// Simple updates
const set1Promise = storage.set('bar', 'foo');
const set2Promise = storage.set('barNumber', 55);
const set3Promise = storage.set('barBoolean', true);
let flushPromiseResolved = false;
storage.whenFlushed().then(() => flushPromiseResolved = true);
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.getNumber('barNumber'), 55);
strictEqual(storage.getBoolean('barBoolean'), true);
strictEqual(changes.size, 3);
ok(changes.has('bar'));
ok(changes.has('barNumber'));
ok(changes.has('barBoolean'));
let setPromiseResolved = false;
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
strictEqual(setPromiseResolved, true);
strictEqual(flushPromiseResolved, true);
changes = new Set<string>();
// Does not trigger events for same update values
storage.set('bar', 'foo');
storage.set('barNumber', 55);
storage.set('barBoolean', true);
strictEqual(changes.size, 0);
// Simple deletes
const delete1Promise = storage.delete('bar');
const delete2Promise = storage.delete('barNumber');
const delete3Promise = storage.delete('barBoolean');
ok(!storage.get('bar'));
ok(!storage.getNumber('barNumber'));
ok(!storage.getBoolean('barBoolean'));
strictEqual(changes.size, 3);
ok(changes.has('bar'));
ok(changes.has('barNumber'));
ok(changes.has('barBoolean'));
changes = new Set<string>();
// Does not trigger events for same delete values
storage.delete('bar');
storage.delete('barNumber');
storage.delete('barBoolean');
strictEqual(changes.size, 0);
let deletePromiseResolved = false;
await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true);
strictEqual(deletePromiseResolved, true);
await storage.close();
await storage.close(); // it is ok to call this multiple times
});
test('external changes', async () => {
test('external changes', () => {
return runWithFakedTimers({}, async function () {
class TestSQLiteStorageDatabase extends SQLiteStorageDatabase {
private readonly _onDidChangeItemsExternal = new Emitter<IStorageItemsChangeEvent>();
override get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return this._onDidChangeItemsExternal.event; }
class TestSQLiteStorageDatabase extends SQLiteStorageDatabase {
private readonly _onDidChangeItemsExternal = new Emitter<IStorageItemsChangeEvent>();
override get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return this._onDidChangeItemsExternal.event; }
fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void {
this._onDidChangeItemsExternal.fire(event);
fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void {
this._onDidChangeItemsExternal.fire(event);
}
}
}
const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db'));
const storage = new Storage(database);
const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db'));
const storage = new Storage(database);
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
});
await storage.init();
await storage.set('foo', 'bar');
ok(changes.has('foo'));
changes.clear();
// Nothing happens if changing to same value
const changed = new Map<string, string>();
changed.set('foo', 'bar');
database.fireDidChangeItemsExternal({ changed });
strictEqual(changes.size, 0);
// Change is accepted if valid
changed.set('foo', 'bar1');
database.fireDidChangeItemsExternal({ changed });
ok(changes.has('foo'));
strictEqual(storage.get('foo'), 'bar1');
changes.clear();
// Delete is accepted
const deleted = new Set<string>(['foo']);
database.fireDidChangeItemsExternal({ deleted });
ok(changes.has('foo'));
strictEqual(storage.get('foo', undefined), undefined);
changes.clear();
// Nothing happens if changing to same value
database.fireDidChangeItemsExternal({ deleted });
strictEqual(changes.size, 0);
strictEqual(isStorageItemsChangeEvent({ changed }), true);
strictEqual(isStorageItemsChangeEvent({ deleted }), true);
strictEqual(isStorageItemsChangeEvent({ changed, deleted }), true);
strictEqual(isStorageItemsChangeEvent(undefined), false);
strictEqual(isStorageItemsChangeEvent({ changed: 'yes', deleted: false }), false);
await storage.close();
});
await storage.init();
await storage.set('foo', 'bar');
ok(changes.has('foo'));
changes.clear();
// Nothing happens if changing to same value
const changed = new Map<string, string>();
changed.set('foo', 'bar');
database.fireDidChangeItemsExternal({ changed });
strictEqual(changes.size, 0);
// Change is accepted if valid
changed.set('foo', 'bar1');
database.fireDidChangeItemsExternal({ changed });
ok(changes.has('foo'));
strictEqual(storage.get('foo'), 'bar1');
changes.clear();
// Delete is accepted
const deleted = new Set<string>(['foo']);
database.fireDidChangeItemsExternal({ deleted });
ok(changes.has('foo'));
strictEqual(storage.get('foo', undefined), undefined);
changes.clear();
// Nothing happens if changing to same value
database.fireDidChangeItemsExternal({ deleted });
strictEqual(changes.size, 0);
strictEqual(isStorageItemsChangeEvent({ changed }), true);
strictEqual(isStorageItemsChangeEvent({ deleted }), true);
strictEqual(isStorageItemsChangeEvent({ changed, deleted }), true);
strictEqual(isStorageItemsChangeEvent(undefined), false);
strictEqual(isStorageItemsChangeEvent({ changed: 'yes', deleted: false }), false);
await storage.close();
});
test('close flushes data', async () => {
@@ -218,72 +222,76 @@ flakySuite('Storage Library', function () {
await storage.close();
});
test('conflicting updates', async () => {
let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
await storage.init();
test('conflicting updates', () => {
return runWithFakedTimers({}, async function () {
let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
await storage.init();
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
let changes = new Set<string>();
storage.onDidChangeStorage(key => {
changes.add(key);
});
const set1Promise = storage.set('foo', 'bar1');
const set2Promise = storage.set('foo', 'bar2');
const set3Promise = storage.set('foo', 'bar3');
let flushPromiseResolved = false;
storage.whenFlushed().then(() => flushPromiseResolved = true);
strictEqual(storage.get('foo'), 'bar3');
strictEqual(changes.size, 1);
ok(changes.has('foo'));
let setPromiseResolved = false;
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
ok(setPromiseResolved);
ok(flushPromiseResolved);
changes = new Set<string>();
const set4Promise = storage.set('bar', 'foo');
const delete1Promise = storage.delete('bar');
ok(!storage.get('bar'));
strictEqual(changes.size, 1);
ok(changes.has('bar'));
let setAndDeletePromiseResolved = false;
await Promise.all([set4Promise, delete1Promise]).then(() => setAndDeletePromiseResolved = true);
ok(setAndDeletePromiseResolved);
await storage.close();
});
const set1Promise = storage.set('foo', 'bar1');
const set2Promise = storage.set('foo', 'bar2');
const set3Promise = storage.set('foo', 'bar3');
let flushPromiseResolved = false;
storage.whenFlushed().then(() => flushPromiseResolved = true);
strictEqual(storage.get('foo'), 'bar3');
strictEqual(changes.size, 1);
ok(changes.has('foo'));
let setPromiseResolved = false;
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
ok(setPromiseResolved);
ok(flushPromiseResolved);
changes = new Set<string>();
const set4Promise = storage.set('bar', 'foo');
const delete1Promise = storage.delete('bar');
ok(!storage.get('bar'));
strictEqual(changes.size, 1);
ok(changes.has('bar'));
let setAndDeletePromiseResolved = false;
await Promise.all([set4Promise, delete1Promise]).then(() => setAndDeletePromiseResolved = true);
ok(setAndDeletePromiseResolved);
await storage.close();
});
test('corrupt DB recovers', async () => {
const storageFile = join(testDir, 'storage.db');
return runWithFakedTimers({}, async function () {
const storageFile = join(testDir, 'storage.db');
let storage = new Storage(new SQLiteStorageDatabase(storageFile));
await storage.init();
let storage = new Storage(new SQLiteStorageDatabase(storageFile));
await storage.init();
await storage.set('bar', 'foo');
await storage.set('bar', 'foo');
await Promises.writeFile(storageFile, 'This is a broken DB');
await Promises.writeFile(storageFile, 'This is a broken DB');
await storage.set('foo', 'bar');
await storage.set('foo', 'bar');
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.get('foo'), 'bar');
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.get('foo'), 'bar');
await storage.close();
await storage.close();
storage = new Storage(new SQLiteStorageDatabase(storageFile));
await storage.init();
storage = new Storage(new SQLiteStorageDatabase(storageFile));
await storage.init();
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.get('foo'), 'bar');
strictEqual(storage.get('bar'), 'foo');
strictEqual(storage.get('foo'), 'bar');
await storage.close();
await storage.close();
});
});
});
@@ -657,27 +665,27 @@ flakySuite('SQLite Storage Library', function () {
storage.set('foo', 'bar');
storage.set('some/foo/path', 'some/bar/path');
await timeout(10);
await timeout(2);
storage.set('foo1', 'bar');
storage.set('some/foo1/path', 'some/bar/path');
await timeout(10);
await timeout(2);
storage.set('foo2', 'bar');
storage.set('some/foo2/path', 'some/bar/path');
await timeout(10);
await timeout(2);
storage.delete('foo1');
storage.delete('some/foo1/path');
await timeout(10);
await timeout(2);
storage.delete('foo4');
storage.delete('some/foo4/path');
await timeout(70);
await timeout(5);
storage.set('foo3', 'bar');
await storage.set('some/foo3/path', 'some/bar/path');
+19
View File
@@ -1189,6 +1189,25 @@ suite('Map', () => {
assert.strictEqual(map.get(windowsFile), 'true');
assert.strictEqual(map.get(uncFile), 'true');
});
test('ResourceMap - files (ignorecase, BUT preservecase)', function () {
const map = new ResourceMap<number>(uri => extUriIgnorePathCase.getComparisonKey(uri));
const fileA = URI.parse('file://some/filea');
const fileAUpper = URI.parse('file://SOME/FILEA');
map.set(fileA, 1);
assert.strictEqual(map.get(fileA), 1);
assert.strictEqual(map.get(fileAUpper), 1);
assert.deepStrictEqual(Array.from(map.keys()).map(String), [fileA].map(String));
assert.deepStrictEqual(Array.from(map), [[fileA, 1]]);
map.set(fileAUpper, 1);
assert.strictEqual(map.get(fileA), 1);
assert.strictEqual(map.get(fileAUpper), 1);
assert.deepStrictEqual(Array.from(map.keys()).map(String), [fileAUpper].map(String));
assert.deepStrictEqual(Array.from(map), [[fileAUpper, 1]]);
});
});
@@ -94,6 +94,7 @@ import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from '
import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProcessTunnelService';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
import { IUserConfigurationFileService, UserConfigurationFileServiceId } from 'vs/platform/configuration/common/userConfigurationFileService';
class SharedProcessMain extends Disposable {
@@ -218,6 +219,9 @@ class SharedProcessMain extends Disposable {
storageService.initialize()
]);
// User Configuration File
services.set(IUserConfigurationFileService, ProxyChannel.toService<IUserConfigurationFileService>(mainProcessService.getChannel(UserConfigurationFileServiceId)));
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
+4 -2
View File
@@ -572,8 +572,10 @@ export class CodeApplication extends Disposable {
const fileSystemProviderChannel = new DiskFileSystemProviderChannel(diskFileSystemProvider, this.logService);
mainProcessElectronServer.registerChannel('localFilesystem', fileSystemProviderChannel);
// Configuration
mainProcessElectronServer.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(new UserConfigurationFileService(this.environmentMainService, this.fileService, this.logService)));
// User Configuration File
const userConfigurationFileService = new UserConfigurationFileService(this.environmentMainService, this.fileService, this.logService);
mainProcessElectronServer.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(userConfigurationFileService));
sharedProcessClient.then(client => client.registerChannel(UserConfigurationFileServiceId, ProxyChannel.fromService(userConfigurationFileService)));
// Update
const updateChannel = new UpdateChannel(accessor.get(IUpdateService));
@@ -5,7 +5,7 @@
import 'vs/css!./indentGuides';
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
import { editorActiveIndentGuides, editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry';
import { editorActiveIndentGuides, editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6, editorBracketPairGuideActiveBackground1, editorBracketPairGuideActiveBackground2, editorBracketPairGuideActiveBackground3, editorBracketPairGuideActiveBackground4, editorBracketPairGuideActiveBackground5, editorBracketPairGuideActiveBackground6, editorBracketPairGuideBackground1, editorBracketPairGuideBackground2, editorBracketPairGuideBackground3, editorBracketPairGuideBackground4, editorBracketPairGuideBackground5, editorBracketPairGuideBackground6, editorIndentGuides } from 'vs/editor/common/view/editorColorRegistry';
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
@@ -16,6 +16,7 @@ import { HorizontalGuidesState, IndentGuide } from 'vs/editor/common/model';
import { ArrayQueue } from 'vs/base/common/arrays';
import { BracketPairGuidesClassNames } from 'vs/editor/common/model/textModel';
import { Color } from 'vs/base/common/color';
import { isDefined } from 'vs/base/common/types';
export class IndentGuidesOverlay extends DynamicViewOverlay {
@@ -241,6 +242,13 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
}
}
function transparentToUndefined(color: Color | undefined): Color | undefined {
if (color && color.isTransparent()) {
return undefined;
}
return color;
}
registerThemingParticipant((theme, collector) => {
const editorIndentGuidesColor = theme.getColor(editorIndentGuides);
if (editorIndentGuidesColor) {
@@ -252,26 +260,48 @@ registerThemingParticipant((theme, collector) => {
}
const colors = [
editorBracketHighlightingForeground1,
editorBracketHighlightingForeground2,
editorBracketHighlightingForeground3,
editorBracketHighlightingForeground4,
editorBracketHighlightingForeground5,
editorBracketHighlightingForeground6
{ bracketColor: editorBracketHighlightingForeground1, guideColor: editorBracketPairGuideBackground1, guideColorActive: editorBracketPairGuideActiveBackground1 },
{ bracketColor: editorBracketHighlightingForeground2, guideColor: editorBracketPairGuideBackground2, guideColorActive: editorBracketPairGuideActiveBackground2 },
{ bracketColor: editorBracketHighlightingForeground3, guideColor: editorBracketPairGuideBackground3, guideColorActive: editorBracketPairGuideActiveBackground3 },
{ bracketColor: editorBracketHighlightingForeground4, guideColor: editorBracketPairGuideBackground4, guideColorActive: editorBracketPairGuideActiveBackground4 },
{ bracketColor: editorBracketHighlightingForeground5, guideColor: editorBracketPairGuideBackground5, guideColorActive: editorBracketPairGuideActiveBackground5 },
{ bracketColor: editorBracketHighlightingForeground6, guideColor: editorBracketPairGuideBackground6, guideColorActive: editorBracketPairGuideActiveBackground6 }
];
const colorProvider = new BracketPairGuidesClassNames();
let colorValues = colors
.map(c => theme.getColor(c))
.filter((c): c is Color => !!c)
.filter(c => !c.isTransparent());
.map(c => {
const bracketColor = theme.getColor(c.bracketColor);
const guideColor = theme.getColor(c.guideColor);
const guideColorActive = theme.getColor(c.guideColorActive);
for (let level = 0; level < 30; level++) {
const color = colorValues[level % colorValues.length];
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.vertical { opacity: 0.3; box-shadow: 1px 0 0 0 ${color} inset; }`);
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.horizontal-top { opacity: 0.3; border-top: 1px solid ${color}; }`);
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')}.horizontal-bottom { opacity: 0.3; border-bottom: 1px solid ${color}; }`);
const effectiveGuideColor = transparentToUndefined(transparentToUndefined(guideColor) ?? bracketColor?.transparent(0.3));
const effectiveGuideColorActive = transparentToUndefined(transparentToUndefined(guideColorActive) ?? bracketColor);
if (!effectiveGuideColor || !effectiveGuideColorActive) {
return undefined;
}
return {
guideColor: effectiveGuideColor,
guideColorActive: effectiveGuideColorActive,
};
})
.filter(isDefined);
if (colorValues.length > 0) {
for (let level = 0; level < 30; level++) {
const colors = colorValues[level % colorValues.length];
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level).replace(/ /g, '.')} { --guide-color: ${colors.guideColor}; --guide-color-active: ${colors.guideColorActive}; }`);
}
collector.addRule(`.monaco-editor .vertical { box-shadow: 1px 0 0 0 var(--guide-color) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top { border-top: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .horizontal-bottom { border-bottom: 1px solid var(--guide-color); }`);
collector.addRule(`.monaco-editor .vertical.${colorProvider.activeClassName} { box-shadow: 1px 0 0 0 var(--guide-color-active) inset; }`);
collector.addRule(`.monaco-editor .horizontal-top.${colorProvider.activeClassName} { border-top: 1px solid var(--guide-color-active); }`);
collector.addRule(`.monaco-editor .horizontal-bottom.${colorProvider.activeClassName} { border-bottom: 1px solid var(--guide-color-active); }`);
}
collector.addRule(`.monaco-editor .${colorProvider.activeClassName} { opacity: 1 !important; }`);
});
+28 -16
View File
@@ -3098,9 +3098,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
endLineNumber,
this.getLineMaxColumn(endLineNumber)
)
)
// Exclude bracket pairs that are not balanced.
.filter(b => b.closingBracketRange !== undefined);
);
let activeBracketPairRange: Range | undefined = undefined;
if (activePosition && bracketPairs.length > 0) {
@@ -3136,7 +3134,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
visibleEndColumn: number,
bracketPair: BracketPairInfo,
renderHorizontalEndLineAtTheBottom: boolean
}>();
} | null>();
const nextGuides = new Array<model.IndentGuide>();
const colorProvider = new BracketPairGuidesClassNames();
@@ -3169,19 +3167,30 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
// TODO: Consider indentation when computing guideVisibleColumn
const start = pair.openingBracketRange.getStartPosition();
const end = (pair.closingBracketRange?.getStartPosition() ?? pair.range.getEndPosition());
activeGuides[pair.nestingLevel] = {
nestingLevel: pair.nestingLevel,
guideVisibleColumn,
start,
visibleStartColumn: this.getVisibleColumnFromPosition(start),
end,
visibleEndColumn: this.getVisibleColumnFromPosition(end),
bracketPair: pair,
renderHorizontalEndLineAtTheBottom
};
if (pair.closingBracketRange === undefined) {
// Don't show guides for bracket pairs that are not balanced.
// See #135125.
activeGuides[pair.nestingLevel] = null;
} else {
activeGuides[pair.nestingLevel] = {
nestingLevel: pair.nestingLevel,
guideVisibleColumn,
start,
visibleStartColumn: this.getVisibleColumnFromPosition(start),
end,
visibleEndColumn: this.getVisibleColumnFromPosition(end),
bracketPair: pair,
renderHorizontalEndLineAtTheBottom
};
}
}
for (const line of activeGuides) {
if (!line) {
continue;
}
const isActive = activeBracketPairRange && line.bracketPair.range.equalsRange(activeBracketPairRange);
const className =
@@ -3214,7 +3223,9 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
// Going backwards, so the last guide potentially replaces others
for (let i = activeGuides.length - 1; i >= 0; i--) {
const line = activeGuides[i];
if (!line) {
continue;
}
const isActive = options.highlightActive && activeBracketPairRange &&
line.bracketPair.range.equalsRange(activeBracketPairRange);
@@ -3233,7 +3244,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
continue;
}
if (line.guideVisibleColumn > lastVisibleColumnCount) {
if (line.guideVisibleColumn >= lastVisibleColumnCount && !isActive) {
// Don't render a guide on top of an existing guide, unless it is active.
continue;
}
lastVisibleColumnCount = line.guideVisibleColumn;
@@ -61,6 +61,21 @@ export const editorBracketHighlightingForeground6 = registerColor('editorBracket
export const editorBracketHighlightingUnexpectedBracketForeground = registerColor('editorBracketHighlight.unexpectedBracket.foreground', { dark: new Color(new RGBA(255, 18, 18, 0.8)), light: new Color(new RGBA(255, 18, 18, 0.8)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('editorBracketHighlightUnexpectedBracketForeground', 'Foreground color of unexpected brackets.'));
export const editorBracketPairGuideBackground1 = registerColor('editorBracketPairGuide.background1', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background1', 'Background color of inactive bracket pair guides (1). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground2 = registerColor('editorBracketPairGuide.background2', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background2', 'Background color of inactive bracket pair guides (2). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground3 = registerColor('editorBracketPairGuide.background3', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background3', 'Background color of inactive bracket pair guides (3). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground4 = registerColor('editorBracketPairGuide.background4', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background4', 'Background color of inactive bracket pair guides (4). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground5 = registerColor('editorBracketPairGuide.background5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background5', 'Background color of inactive bracket pair guides (5). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideBackground6 = registerColor('editorBracketPairGuide.background6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.background6', 'Background color of inactive bracket pair guides (6). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground1 = registerColor('editorBracketPairGuide.activeBackground1', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground1', 'Background color of active bracket pair guides (1). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground2 = registerColor('editorBracketPairGuide.activeBackground2', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground2', 'Background color of active bracket pair guides (2). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground3 = registerColor('editorBracketPairGuide.activeBackground3', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground3', 'Background color of active bracket pair guides (3). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground4 = registerColor('editorBracketPairGuide.activeBackground4', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground4', 'Background color of active bracket pair guides (4). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground5 = registerColor('editorBracketPairGuide.activeBackground5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground5', 'Background color of active bracket pair guides (5). Requires enabling bracket pair guides.'));
export const editorBracketPairGuideActiveBackground6 = registerColor('editorBracketPairGuide.activeBackground6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketPairGuide.activeBackground6', 'Background color of active bracket pair guides (6). Requires enabling bracket pair guides.'));
// contains all color rules that used to defined in editor/browser/widget/editor.css
registerThemingParticipant((theme, collector) => {
const background = theme.getColor(editorBackground);
@@ -44,6 +44,11 @@ class CodeLensViewZone implements IViewZone {
this._onHeight();
}
}
isVisible(): boolean {
return this._lastHeight !== 0
&& this.domNode.hasAttribute('monaco-visible-view-zone');
}
}
class CodeLensContentWidget implements IContentWidget {
@@ -289,7 +294,7 @@ export class CodeLensWidget {
}
computeIfNecessary(model: ITextModel): CodeLensItem[] | null {
if (!this._viewZone.domNode.hasAttribute('monaco-visible-view-zone')) {
if (!this._viewZone.isVisible()) {
return null;
}
@@ -94,7 +94,7 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
return undefined;
}
const valid =
normalizedSuggestItem.range.equalsRange(normalizedItemToPreselect.range) &&
rangeStartsWith(normalizedItemToPreselect.range, normalizedSuggestItem.range) &&
normalizedItemToPreselect.text.startsWith(normalizedSuggestItem.text);
return { index, valid, prefixLength: normalizedSuggestItem.text.length, suggestItem };
})
@@ -190,6 +190,16 @@ export class SuggestWidgetInlineCompletionProvider extends Disposable {
}
}
function rangeStartsWith(rangeToTest: Range, prefix: Range): boolean {
return (
rangeToTest.startLineNumber === prefix.startLineNumber &&
rangeToTest.startColumn === prefix.startColumn &&
(rangeToTest.endLineNumber < prefix.endLineNumber ||
(rangeToTest.endLineNumber === prefix.endLineNumber &&
rangeToTest.endColumn <= prefix.endColumn))
);
}
function suggestionToInlineCompletion(suggestController: SuggestController, position: Position, item: CompletionItem, toggleMode: boolean): NormalizedInlineCompletion | undefined {
// additionalTextEdits might not be resolved here, this could be problematic.
if (Array.isArray(item.completion.additionalTextEdits) && item.completion.additionalTextEdits.length > 0) {
@@ -8,9 +8,8 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { JSONPath, parse, ParseError } from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Edit, FormattingOptions } from 'vs/base/common/jsonFormatter';
import { URI } from 'vs/base/common/uri';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { FileOperationError, FileOperationResult, IFileService, IWriteFileOptions } from 'vs/platform/files/common/files';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
@@ -31,6 +30,7 @@ export interface IUserConfigurationFileService {
readonly _serviceBrand: undefined;
updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void>;
write(value: VSBuffer, options?: IWriteFileOptions): Promise<void>;
}
export class UserConfigurationFileService implements IUserConfigurationFileService {
@@ -48,12 +48,12 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi
}
async updateSettings(value: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
return this.queue.queue(() => this.doWrite(this.environmentService.settingsResource, value, formattingOptions)); // queue up writes to prevent race conditions
return this.queue.queue(() => this.doWrite(value, formattingOptions)); // queue up writes to prevent race conditions
}
private async doWrite(resource: URI, jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
this.logService.trace(`${UserConfigurationFileServiceId}#write`, resource.toString(), jsonValue);
const { value, mtime, etag } = await this.fileService.readFile(resource, { atomic: true });
private async doWrite(jsonValue: IJSONValue, formattingOptions: FormattingOptions): Promise<void> {
this.logService.trace(`${UserConfigurationFileServiceId}#write`, this.environmentService.settingsResource.toString(), jsonValue);
const { value, mtime, etag } = await this.fileService.readFile(this.environmentService.settingsResource, { atomic: true });
let content = value.toString();
const parseErrors: ParseError[] = [];
@@ -66,7 +66,7 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi
if (edit) {
content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length);
try {
await this.fileService.writeFile(resource, VSBuffer.fromString(content), { etag, mtime });
await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(content), { etag, mtime });
} catch (error) {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) {
throw new Error(UserConfigurationErrorCode.ERROR_FILE_MODIFIED_SINCE);
@@ -75,6 +75,13 @@ export class UserConfigurationFileService implements IUserConfigurationFileServi
}
}
async write(content: VSBuffer, options?: IWriteFileOptions): Promise<void> {
// queue up writes to prevent race conditions
return this.queue.queue(async () => {
await this.fileService.writeFile(this.environmentService.settingsResource, content, options);
});
}
private getEdits({ value, path }: IJSONValue, modelContent: string, formattingOptions: FormattingOptions): Edit[] {
if (path.length) {
return setProperty(modelContent, path, value, formattingOptions);
@@ -8,6 +8,7 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { Storage } from 'vs/base/parts/storage/common/storage';
import { flakySuite } from 'vs/base/test/common/testUtils';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { FileService } from 'vs/platform/files/common/fileService';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { NullLogService } from 'vs/platform/log/common/log';
@@ -66,20 +67,22 @@ flakySuite('StorageService (browser specific)', () => {
disposables.clear();
});
test('clear', async () => {
storageService.store('bar', 'foo', StorageScope.GLOBAL, StorageTarget.MACHINE);
storageService.store('bar', 3, StorageScope.GLOBAL, StorageTarget.USER);
storageService.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE);
storageService.store('bar', 3, StorageScope.WORKSPACE, StorageTarget.USER);
test('clear', () => {
return runWithFakedTimers({ useFakeTimers: true }, async () => {
storageService.store('bar', 'foo', StorageScope.GLOBAL, StorageTarget.MACHINE);
storageService.store('bar', 3, StorageScope.GLOBAL, StorageTarget.USER);
storageService.store('bar', 'foo', StorageScope.WORKSPACE, StorageTarget.MACHINE);
storageService.store('bar', 3, StorageScope.WORKSPACE, StorageTarget.USER);
await storageService.clear();
await storageService.clear();
for (const scope of [StorageScope.GLOBAL, StorageScope.WORKSPACE]) {
for (const target of [StorageTarget.USER, StorageTarget.MACHINE]) {
strictEqual(storageService.get('bar', scope), undefined);
strictEqual(storageService.keys(scope, target).length, 0);
for (const scope of [StorageScope.GLOBAL, StorageScope.WORKSPACE]) {
for (const target of [StorageTarget.USER, StorageTarget.MACHINE]) {
strictEqual(storageService.get('bar', scope), undefined);
strictEqual(storageService.keys(scope, target).length, 0);
}
}
}
});
});
});
@@ -760,7 +760,7 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
try {
if (oldContent) {
// file exists already
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent);
await this.writeFileContent(newContent, oldContent, force);
} else {
// file does not exist
await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: force });
@@ -775,6 +775,10 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser {
}
}
protected async writeFileContent(newContent: string, oldContent: IFileContent, force: boolean): Promise<void> {
await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), force ? undefined : oldContent);
}
private onFileChanges(e: FileChangesEvent): void {
if (!e.contains(this.file)) {
return;
@@ -12,9 +12,10 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels';
import { IUserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { FileOperationError, FileOperationResult, IFileContent, IFileService } from 'vs/platform/files/common/files';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { AbstractInitializer, AbstractJsonFileSynchroniser, IAcceptResult, IFileResourcePreview, IMergeResult } from 'vs/platform/userDataSync/common/abstractSynchronizer';
@@ -62,6 +63,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
@IUserDataSyncResourceEnablementService userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
@ITelemetryService telemetryService: ITelemetryService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IUserConfigurationFileService private readonly userConfigurationFileService: IUserConfigurationFileService,
) {
super(environmentService.settingsResource, SyncResource.Settings, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncResourceEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService);
}
@@ -325,6 +327,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content);
}
protected override async writeFileContent(newContent: string, oldContent: IFileContent, force: boolean): Promise<void> {
await this.userConfigurationFileService.write(VSBuffer.fromString(newContent), force ? undefined : { etag: oldContent.etag, mtime: oldContent.mtime });
}
private validateContent(content: string): void {
if (this.hasErrors(content)) {
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.resource);
@@ -16,6 +16,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/common/configurationService';
import { IUserConfigurationFileService, UserConfigurationFileService } from 'vs/platform/configuration/common/userConfigurationFileService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { DidUninstallExtensionEvent, IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
@@ -88,6 +89,7 @@ export class UserDataSyncClient extends Disposable {
const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService));
await configurationService.initialize();
this.instantiationService.stub(IConfigurationService, configurationService);
this.instantiationService.stub(IUserConfigurationFileService, this.instantiationService.createInstance(UserConfigurationFileService));
this.instantiationService.stub(IRequestService, this.testServer);
+3 -2
View File
@@ -12734,8 +12734,9 @@ declare module 'vscode' {
* The UI-visible count of {@link SourceControlResourceState resource states} of
* this source control.
*
* Equals to the total number of {@link SourceControlResourceState resource state}
* of this source control, if undefined.
* If undefined, this source control will
* - display its UI-visible count as zero, and
* - contribute the count of its {@link SourceControlResourceState resource states} to the UI-visible aggregated count for all source controls
*/
count?: number;
@@ -16,13 +16,12 @@ import { ResourceMap } from 'vs/base/common/map';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo';
import { IExtUri } from 'vs/base/common/resources';
import { SkipList } from 'vs/base/common/skipList';
export class DiagnosticCollection implements vscode.DiagnosticCollection {
readonly #proxy: MainThreadDiagnosticsShape | undefined;
readonly #onDidChangeDiagnostics: Emitter<vscode.Uri[]>;
readonly #data: SkipList<URI, vscode.Diagnostic[]>;
readonly #data: ResourceMap<vscode.Diagnostic[]>;
private _isDisposed = false;
@@ -34,7 +33,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
proxy: MainThreadDiagnosticsShape | undefined,
onDidChangeDiagnostics: Emitter<vscode.Uri[]>
) {
this.#data = new SkipList((a, b) => extUri.compare(a, b));
this.#data = new ResourceMap(uri => extUri.getComparisonKey(uri));
this.#proxy = proxy;
this.#onDidChangeDiagnostics = onDidChangeDiagnostics;
}
+1 -1
View File
@@ -33,7 +33,7 @@ export const OpenFolderWorkspaceSupportContext = new RawContextKey<boolean>('ope
export const EnterMultiRootWorkspaceSupportContext = new RawContextKey<boolean>('enterMultiRootWorkspaceSupport', true, true);
export const EmptyWorkspaceSupportContext = new RawContextKey<boolean>('emptyWorkspaceSupport', true, true);
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any dirty working copies"));
export const DirtyWorkingCopiesContext = new RawContextKey<boolean>('dirtyWorkingCopies', false, localize('dirtyWorkingCopies', "Whether there are any working copies with unsaved changes"));
export const RemoteNameContext = new RawContextKey<string>('remoteName', '', localize('remoteName', "The name of the remote the window is connected to or an empty string if not connected to any remote"));
export const VirtualWorkspaceContext = new RawContextKey<string>('virtualWorkspace', '', localize('virtualWorkspace', "The scheme of the current workspace if is from a virtual file system or an empty string."));
@@ -62,7 +62,7 @@ class OutlineItem extends BreadcrumbsItem {
if (!(other instanceof OutlineItem)) {
return false;
}
return this.element === other.element &&
return this.element.element === other.element.element &&
this.options.showFileIcons === other.options.showFileIcons &&
this.options.showSymbolIcons === other.options.showSymbolIcons;
}
@@ -149,11 +149,11 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
ariaLabel: (() => {
if (mapGroupIdToGroupAriaLabel.size > 1) {
return isDirty ?
localize('entryAriaLabelWithGroupDirty', "{0}, dirty, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId)) :
localize('entryAriaLabelWithGroupDirty', "{0}, unsaved changes, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId)) :
localize('entryAriaLabelWithGroup', "{0}, {1}", nameAndDescription, mapGroupIdToGroupAriaLabel.get(groupId));
}
return isDirty ? localize('entryAriaLabelDirty', "{0}, dirty", nameAndDescription) : nameAndDescription;
return isDirty ? localize('entryAriaLabelDirty', "{0}, unsaved changes", nameAndDescription) : nameAndDescription;
})(),
description,
iconClasses: getIconClasses(this.modelService, this.modeService, resource).concat(editor.getLabelExtraClasses()),
@@ -457,9 +457,10 @@ export class ViewsService extends Disposable implements IViewsService {
private registerFocusViewAction(viewDescriptor: IViewDescriptor, category?: string | ILocalizedString): IDisposable {
return registerAction2(class FocusViewAction extends Action2 {
constructor() {
const title = localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name);
super({
id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`,
title: { original: `Focus on ${viewDescriptor.name} View`, value: localize({ key: 'focus view', comment: ['{0} indicates the name of the view to be focused.'] }, "Focus on {0} View", viewDescriptor.name) },
title: { original: `Focus on ${viewDescriptor.name} View`, value: title },
category,
menu: [{
id: MenuId.CommandPalette,
@@ -473,11 +474,29 @@ export class ViewsService extends Disposable implements IViewsService {
linux: viewDescriptor.focusCommand?.keybindings?.linux,
mac: viewDescriptor.focusCommand?.keybindings?.mac,
win: viewDescriptor.focusCommand?.keybindings?.win
},
description: {
description: title,
args: [
{
name: 'focusOptions',
description: 'Focus Options',
schema: {
type: 'object',
properties: {
'preserveFocus': {
type: 'boolean',
default: false
}
},
}
}
]
}
});
}
run(accessor: ServicesAccessor): void {
accessor.get(IViewsService).openView(viewDescriptor.id, true);
run(accessor: ServicesAccessor, options?: { preserveFocus?: boolean }): void {
accessor.get(IViewsService).openView(viewDescriptor.id, !options?.preserveFocus);
}
});
}
@@ -47,7 +47,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
},
'workbench.editor.highlightModifiedTabs': {
'type': 'boolean',
'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
'markdownDescription': localize('highlightModifiedTabs', "Controls whether a top border is drawn on tabs for editors that have unsaved changes. This value is ignored when `#workbench.editor.showTabs#` is disabled."),
'default': false
},
'workbench.editor.decorations.badges': {
@@ -169,7 +169,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
},
'workbench.editor.closeOnFileDelete': {
'type': 'boolean',
'description': localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."),
'description': localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that editors with unsaved changes will never close to preserve your data."),
'default': false
},
'workbench.editor.openPositioning': {
@@ -228,7 +228,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'workbench.editor.limit.enabled': {
'type': 'boolean',
'default': false,
'description': localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors that are not dirty will close to make space for newly opening editors.")
'description': localize('limitEditorsEnablement', "Controls if the number of opened editors should be limited or not. When enabled, less recently used editors will close to make space for newly opening editors.")
},
'workbench.editor.limit.value': {
'type': 'number',
@@ -381,7 +381,7 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
localize('rootPath', "`${rootPath}`: file path of the opened workspace or folder (e.g. /Users/Development/myWorkspace)."),
localize('appName', "`${appName}`: e.g. VS Code."),
localize('remoteName', "`${remoteName}`: e.g. SSH"),
localize('dirty', "`${dirty}`: a dirty indicator if the active editor is dirty."),
localize('dirty', "`${dirty}`: an indicator for when the active editor has unsaved changes."),
localize('separator', "`${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.")
].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations
+1 -1
View File
@@ -38,7 +38,7 @@ export const DEFAULT_EDITOR_ASSOCIATION = {
};
// Editor State Context Keys
export const ActiveEditorDirtyContext = new RawContextKey<boolean>('activeEditorIsDirty', false, localize('activeEditorIsDirty', "Whether the active editor is dirty"));
export const ActiveEditorDirtyContext = new RawContextKey<boolean>('activeEditorIsDirty', false, localize('activeEditorIsDirty', "Whether the active editor has unsaved changes"));
export const ActiveEditorPinnedContext = new RawContextKey<boolean>('activeEditorIsNotPreview', false, localize('activeEditorIsNotPreview', "Whether the active editor is not in preview mode"));
export const ActiveEditorStickyContext = new RawContextKey<boolean>('activeEditorIsPinned', false, localize('activeEditorIsPinned', "Whether the active editor is pinned"));
export const ActiveEditorReadonlyContext = new RawContextKey<boolean>('activeEditorIsReadonly', false, localize('activeEditorIsReadonly', "Whether the active editor is readonly"));
+4 -4
View File
@@ -165,25 +165,25 @@ export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorde
dark: '#3399CC',
light: '#33AAEE',
hc: null
}, localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
}, localize('tabActiveModifiedBorder', "Border on the top of modified active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', {
dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
hc: Color.white
}, localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
}, localize('tabInactiveModifiedBorder', "Border on the top of modified inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', {
dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5),
light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7),
hc: Color.white
}, localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
}, localize('unfocusedActiveModifiedBorder', "Border on the top of modified active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', {
dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5),
light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5),
hc: Color.white
}, localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
}, localize('unfocusedINactiveModifiedBorder', "Border on the top of modified inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups."));
//#endregion
@@ -1107,11 +1107,18 @@ export class ExtensionEditor extends EditorPane {
$('th', undefined, localize('description', "Description")),
$('th', undefined, localize('default', "Default"))
),
...contrib.map(key => $('tr', undefined,
$('td', undefined, $('code', undefined, key)),
$('td', undefined, properties[key].description || (properties[key].markdownDescription && renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } }))),
$('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`))
))
...contrib.map(key => {
let description: (Node | string) = properties[key].description;
if (properties[key].markdownDescription) {
const { element, dispose } = renderMarkdown({ value: properties[key].markdownDescription }, { actionHandler: { callback: (content) => this.openerService.open(content).catch(onUnexpectedError), disposables: this.contentDisposables } });
description = element;
this.contentDisposables.add(toDisposable(dispose));
}
return $('tr', undefined,
$('td', undefined, $('code', undefined, key)),
$('td', undefined, description),
$('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`)));
})
)
);
@@ -113,7 +113,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ?
'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE],
'default': HotExitConfiguration.ON_EXIT,
'markdownEnumDescriptions': [
nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'),
nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with editors that have unsaved changes.'),
nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`'),
nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. A list of previously opened windows with unsaved files can be accessed via `File > Open Recent > More...`')
],
@@ -124,7 +124,7 @@ const hotExitConfiguration: IConfigurationPropertySchema = isNative ?
'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE],
'default': HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE,
'markdownEnumDescriptions': [
nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with dirty files.'),
nls.localize('hotExit.off', 'Disable hot exit. A prompt will show when attempting to close a window with editors that have unsaved changes.'),
nls.localize('hotExit.onExitAndWindowCloseBrowser', 'Hot exit will be triggered when the browser quits or the window or tab is closed.')
],
'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE)
@@ -229,18 +229,18 @@ configurationRegistry.registerConfiguration({
'type': 'string',
'enum': [AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE],
'markdownEnumDescriptions': [
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "A dirty editor is never automatically saved."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "A dirty editor is automatically saved after the configured `#files.autoSaveDelay#`."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "A dirty editor is automatically saved when the editor loses focus."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "A dirty editor is automatically saved when the window loses focus.")
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "An editor with changes is never automatically saved."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "An editor with changes is automatically saved after the configured `#files.autoSaveDelay#`."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "An editor with changes is automatically saved when the editor loses focus."),
nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "An editor with changes is automatically saved when the window loses focus.")
],
'default': isWeb ? AutoSaveConfiguration.AFTER_DELAY : AutoSaveConfiguration.OFF,
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty editors. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY)
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of editors that have unsaved changes. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY)
},
'files.autoSaveDelay': {
'type': 'number',
'default': 1000,
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty editor is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY)
'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in milliseconds after which an editor with unsaved changes is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY)
},
'files.watcherExclude': {
'type': 'object',
@@ -292,7 +292,8 @@ class DirtyDiffWidget extends PeekViewWidget {
fixedOverflowWidgets: true,
minimap: { enabled: false },
renderSideBySide: false,
readOnly: false
readOnly: false,
renderIndicators: false
};
this.diffEditor = this.instantiationService.createInstance(EmbeddedDiffEditorWidget, container, options, this.editor);
@@ -894,7 +894,7 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
return {
resource,
label,
ariaLabel: isDirty ? localize('filePickAriaLabelDirty', "{0} dirty", labelAndDescription) : labelAndDescription,
ariaLabel: isDirty ? localize('filePickAriaLabelDirty', "{0} unsaved changes", labelAndDescription) : labelAndDescription,
description,
iconClasses: getIconClasses(this.modelService, this.modeService, resource).concat(extraClasses),
buttons: (() => {
@@ -312,7 +312,7 @@ const terminalConfiguration: IConfigurationNode = {
default: 'never'
},
[TerminalSettingId.ConfirmOnKill]: {
description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as dirty when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell."),
description: localize('terminal.integrated.confirmOnKill', "Controls whether to confirm killing terminals when they have child processes. When set to editor, terminals in the editor area will be marked as changed when they have child processes. Note that child process detection may not work well for shells like Git Bash which don't run their processes as child processes of the shell."),
type: 'string',
enum: ['never', 'editor', 'panel', 'always'],
enumDescriptions: [
@@ -214,7 +214,7 @@ abstract class BaseSwitchWindow extends Action2 {
return {
payload: window.id,
label: window.title,
ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, dirty window", window.title) : window.title,
ariaLabel: window.dirty ? localize('windowDirtyAriaLabel', "{0}, window with unsaved changes", window.title) : window.title,
iconClasses: getIconClasses(modelService, modeService, resource, fileKind),
description: (currentWindowId === window.id) ? localize('current', "Current Window") : undefined,
buttons: currentWindowId !== window.id ? window.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined
@@ -387,18 +387,18 @@ export class ConfigurationEditingService {
}
case ConfigurationEditingErrorCode.ERROR_CONFIGURATION_FILE_DIRTY: {
if (operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY) {
return nls.localize('errorTasksConfigurationFileDirty', "Unable to write into tasks configuration file because the file is dirty. Please save it first and then try again.");
return nls.localize('errorTasksConfigurationFileDirty', "Unable to write into tasks configuration file because the file has unsaved changes. Please save it first and then try again.");
}
if (operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY) {
return nls.localize('errorLaunchConfigurationFileDirty', "Unable to write into launch configuration file because the file is dirty. Please save it first and then try again.");
return nls.localize('errorLaunchConfigurationFileDirty', "Unable to write into launch configuration file because the file has unsaved changes. Please save it first and then try again.");
}
switch (target) {
case EditableConfigurationTarget.USER_LOCAL:
return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file is dirty. Please save the user settings file first and then try again.");
return nls.localize('errorConfigurationFileDirty', "Unable to write into user settings because the file has unsaved changes. Please save the user settings file first and then try again.");
case EditableConfigurationTarget.USER_REMOTE:
return nls.localize('errorRemoteConfigurationFileDirty', "Unable to write into remote user settings because the file is dirty. Please save the remote user settings file first and then try again.");
return nls.localize('errorRemoteConfigurationFileDirty', "Unable to write into remote user settings because the file has unsaved changes. Please save the remote user settings file first and then try again.");
case EditableConfigurationTarget.WORKSPACE:
return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file is dirty. Please save the workspace settings file first and then try again.");
return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file has unsaved changes. Please save the workspace settings file first and then try again.");
case EditableConfigurationTarget.WORKSPACE_FOLDER:
let workspaceFolderName: string = '<<unknown>>';
if (operation.resource) {
@@ -407,7 +407,7 @@ export class ConfigurationEditingService {
workspaceFolderName = folder.name;
}
}
return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file is dirty. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName);
return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file has unsaved changes. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName);
default:
return '';
}
@@ -134,7 +134,7 @@ export class JSONEditingService implements IJSONEditingService {
return nls.localize('errorInvalidFile', "Unable to write into the file. Please open the file to correct errors/warnings in the file and try again.");
}
case JSONEditingErrorCode.ERROR_FILE_DIRTY: {
return nls.localize('errorFileDirty', "Unable to write into the file because the file is dirty. Please save the file and try again.");
return nls.localize('errorFileDirty', "Unable to write into the file because the file has unsaved changes. Please save the file and try again.");
}
}
}
@@ -261,7 +261,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
// Extension is disabled. Enable the extension and reload the window to handle.
else if (this.extensionEnablementService.canChangeEnablement(extension)) {
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/enable_extension/accept', { extensionId: extensionIdentifier.id });
this.telemetryService.publicLog2<ExtensionUrlHandlerEvent, ExtensionUrlHandlerClassification>('uri_invoked/enable_extension/start', { extensionId: extensionIdentifier.id });
const result = await this.dialogService.confirm({
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
@@ -256,7 +256,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
// Target cannot be dirty if not writing into buffer
if (this.textFileService.isDirty(this.resource)) {
return Promise.reject(new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.")));
return Promise.reject(new Error(localize('errorKeybindingsFileDirty', "Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again.")));
}
return this.resolveModelReference()
@@ -152,7 +152,7 @@ suite('KeybindingsEditing', () => {
instantiationService.stub(ITextFileService, 'isDirty', true);
return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined)
.then(() => assert.fail('Should fail with dirty error'),
error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.'));
error => assert.strictEqual(error.message, 'Unable to write because the keybindings configuration file has unsaved changes. Please save it first and then try again.'));
});
test('errors cases - did not find an array', async () => {
@@ -115,7 +115,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028)
}
this.showErrorDialog(localize('backupTrackerBackupFailed', "The following dirty editors could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError);
this.showErrorDialog(localize('backupTrackerBackupFailed', "The following editors with unsaved changes could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError);
return true; // veto (the backup failed)
}
@@ -131,7 +131,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028)
}
this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following dirty editors could not be saved or reverted."), remainingDirtyWorkingCopies, error);
this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following editors with unsaved changes could not be saved or reverted."), remainingDirtyWorkingCopies, error);
return true; // veto (save or revert failed)
}
@@ -140,7 +140,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void {
const dirtyWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isDirty());
const advice = localize('backupErrorDetails', "Try saving or reverting the dirty editors first and then try again.");
const advice = localize('backupErrorDetails', "Try saving or reverting the editors with unsaved changes first and then try again.");
const detail = dirtyWorkingCopies.length
? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice
: advice;
@@ -225,8 +225,8 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
error = backupError;
}
},
localize('backupBeforeShutdownMessage', "Backing up dirty editors is taking longer than expected..."),
localize('backupBeforeShutdownDetail', "Click 'Cancel' to stop waiting and to save or revert dirty editors.")
localize('backupBeforeShutdownMessage', "Backing up editors with unsaved changes is taking longer than expected..."),
localize('backupBeforeShutdownDetail', "Click 'Cancel' to stop waiting and to save or revert editors with unsaved changes.")
);
return { backups, error };
@@ -296,7 +296,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
if (result !== false) {
await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : Promise.resolve(true)));
}
}, localize('saveBeforeShutdown', "Saving dirty editors is taking longer than expected..."));
}, localize('saveBeforeShutdown', "Saving editors with unsaved changes is taking longer than expected..."));
}
private doRevertAllBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise<void> {
@@ -313,7 +313,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp
// If we still have dirty working copies, revert those directly
// unless the revert operation was not successful (e.g. cancelled)
await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve()));
}, localize('revertBeforeShutdown', "Reverting dirty editors is taking longer than expected..."));
}, localize('revertBeforeShutdown', "Reverting editors with unsaved changes is taking longer than expected..."));
}
private withProgressAndCancellation(promiseFactory: (token: CancellationToken) => Promise<void>, title: string, detail?: string): Promise<void> {
@@ -307,7 +307,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi
}
private onWorkspaceConfigurationFileDirtyError(): void {
const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again.");
const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file has unsaved changes. Please save it and try again.");
this.askToOpenWorkspaceConfigurationFile(message);
}