diff --git a/extensions/package.json b/extensions/package.json index 65d512cb8a5..f2f1225c829 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.7.3" + "typescript": "3.7.5" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/typescript-language-features/extension.webpack.config.js b/extensions/typescript-language-features/extension.webpack.config.js index de88398eca0..b8f77666761 100644 --- a/extensions/typescript-language-features/extension.webpack.config.js +++ b/extensions/typescript-language-features/extension.webpack.config.js @@ -14,6 +14,9 @@ module.exports = withDefaults({ resolve: { mainFields: ['module', 'main'] }, + externals: { + 'typescript-vscode-sh-plugin': 'commonjs vscode' // used by build/lib/extensions to know what node_modules to bundle + }, entry: { extension: './src/extension.ts', } diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index c6687646d1a..107f968111f 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -27,7 +27,7 @@ "@types/node": "^12.11.7", "@types/rimraf": "2.0.2", "@types/semver": "^5.5.0", - "vscode": "^1.1.10" + "vscode": "^1.1.36" }, "scripts": { "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:typescript ./tsconfig.json" @@ -957,7 +957,8 @@ ], "typescriptServerPlugins": [ { - "name": "typescript-vscode-sh-plugin" + "name": "typescript-vscode-sh-plugin", + "enableForWorkspaceTypeScriptVersions": true } ] } diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index dd281404707..2e905c7d0fa 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -331,7 +331,7 @@ namespace CompletionConfiguration { class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider { - public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<']; + public static readonly triggerCharacters = ['.', '"', '\'', '`', '/', '@', '<', '#']; constructor( private readonly client: ITypeScriptServiceClient, @@ -459,6 +459,18 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider } private getTsTriggerCharacter(context: vscode.CompletionContext): Proto.CompletionsTriggerCharacter | undefined { + // Workaround for https://github.com/microsoft/TypeScript/issues/36234 + if (context.triggerCharacter === '#') { + return undefined; + } + + // Workaround for https://github.com/Microsoft/TypeScript/issues/27321 + if (context.triggerCharacter === '@' + && this.client.apiVersion.gte(API.v310) && this.client.apiVersion.lt(API.v320) + ) { + return undefined; + } + // Workaround for https://github.com/Microsoft/TypeScript/issues/27321 if (context.triggerCharacter === '@' && this.client.apiVersion.gte(API.v310) && this.client.apiVersion.lt(API.v320) diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index 48f9ca27d4a..b63ff25019f 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -72,32 +72,31 @@ export default class TscTaskProvider implements vscode.TaskProvider { return tasks; } - public async resolveTask(_task: vscode.Task): Promise { - const definition = _task.definition; - const badTsconfig = /\\tsconfig.*\.json/; - if (badTsconfig.exec(definition.tsconfig) !== null) { + public async resolveTask(task: vscode.Task): Promise { + const definition = task.definition; + if (/\\tsconfig.*\.json/.test(definition.tsconfig)) { // Warn that the task has the wrong slash type vscode.window.showWarningMessage(localize('badTsConfig', "TypeScript Task in tasks.json contains \"\\\\\". TypeScript tasks tsconfig must use \"/\"")); return undefined; } - const typescriptTask = (_task.definition).tsconfig; - if (typescriptTask) { - if (_task.scope === undefined || _task.scope === vscode.TaskScope.Global || _task.scope === vscode.TaskScope.Workspace) { - // scope is required to be a WorkspaceFolder for resolveTask - return undefined; - } - const kind: TypeScriptTaskDefinition = (_task.definition); - const tsconfigUri: vscode.Uri = _task.scope.uri.with({ path: _task.scope.uri.path + '/' + kind.tsconfig }); - const tsconfig: TSConfig = { - uri: tsconfigUri, - fsPath: tsconfigUri.fsPath, - posixPath: tsconfigUri.path, - workspaceFolder: _task.scope - }; - return this.getTasksForProjectAndDefinition(tsconfig, kind); + const tsconfigPath = definition.tsconfig; + if (!tsconfigPath) { + return undefined; } - return undefined; + + if (task.scope === undefined || task.scope === vscode.TaskScope.Global || task.scope === vscode.TaskScope.Workspace) { + // scope is required to be a WorkspaceFolder for resolveTask + return undefined; + } + const tsconfigUri = task.scope.uri.with({ path: task.scope.uri.path + '/' + tsconfigPath }); + const tsconfig: TSConfig = { + uri: tsconfigUri, + fsPath: tsconfigUri.fsPath, + posixPath: tsconfigUri.path, + workspaceFolder: task.scope + }; + return this.getTasksForProjectAndDefinition(tsconfig, definition); } private async getAllTsConfigs(token: vscode.CancellationToken): Promise { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index f57857724f3..4a5226beea9 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -680,7 +680,7 @@ vscode-test@^0.4.1: http-proxy-agent "^2.1.0" https-proxy-agent "^2.2.1" -vscode@^1.1.10: +vscode@^1.1.36: version "1.1.36" resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== diff --git a/extensions/vscode-account/extension.webpack.config.js b/extensions/vscode-account/extension.webpack.config.js index aa95271f3a3..a513ac5c3b5 100644 --- a/extensions/vscode-account/extension.webpack.config.js +++ b/extensions/vscode-account/extension.webpack.config.js @@ -11,13 +11,10 @@ const withDefaults = require('../shared.webpack.config'); module.exports = withDefaults({ context: __dirname, - resolve: { - mainFields: ['module', 'main'] - }, entry: { extension: './src/extension.ts', }, externals: { - 'keytar': 'commonjs keytar', - }, + 'keytar': 'commonjs keytar' + } }); diff --git a/extensions/vscode-account/package.json b/extensions/vscode-account/package.json index 4ec376e2bb5..35649821a85 100644 --- a/extensions/vscode-account/package.json +++ b/extensions/vscode-account/package.json @@ -1,5 +1,5 @@ { - "name": "login", + "name": "vscode-account", "publisher": "vscode", "displayName": "Account", "description": "", @@ -20,14 +20,11 @@ "compile": "tsc -p ./", "watch": "tsc -watch -p ./" }, - "dependencies": { - "keytar": "^5.0.0" - }, "devDependencies": { "typescript": "^3.7.4", "tslint": "^5.12.1", "@types/node": "^10.12.21", - "@types/keytar": "^4.4.2", + "@types/keytar": "^4.0.1", "@types/vscode": "^1.41.0" } } diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts index 2f1c927acd3..a53d848c76c 100644 --- a/extensions/vscode-account/src/keychain.ts +++ b/extensions/vscode-account/src/keychain.ts @@ -3,16 +3,44 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as keytar from 'keytar'; +// keytar depends on a native module shipped in vscode, so this is +// how we load it +import * as keytarType from 'keytar'; + +function getKeytar(): Keytar | undefined { + try { + return require('keytar'); + } catch (err) { + console.log(err); + } + + return undefined; +} + +export type Keytar = { + getPassword: typeof keytarType['getPassword']; + setPassword: typeof keytarType['setPassword']; + deletePassword: typeof keytarType['deletePassword']; +}; const SERVICE_ID = 'vscode.login'; const ACCOUNT_ID = 'account'; export class Keychain { + private keytar: Keytar; + + constructor() { + const keytar = getKeytar(); + if (!keytar) { + throw new Error('System keychain unavailable'); + } + + this.keytar = keytar; + } async setToken(token: string): Promise { try { - return await keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); + return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); } catch (e) { // Ignore } @@ -20,7 +48,7 @@ export class Keychain { async getToken() { try { - return await keytar.getPassword(SERVICE_ID, ACCOUNT_ID); + return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); } catch (e) { // Ignore } @@ -28,7 +56,7 @@ export class Keychain { async deleteToken() { try { - return await keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); + return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); } catch (e) { // Ignore } diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock index 865a0e74353..4fc295de4b9 100644 --- a/extensions/vscode-account/yarn.lock +++ b/extensions/vscode-account/yarn.lock @@ -18,7 +18,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@types/keytar@^4.4.2": +"@types/keytar@^4.0.1": version "4.4.2" resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.2.tgz#49ef917d6cbb4f19241c0ab50cd35097b5729b32" integrity sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw== @@ -301,7 +301,7 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -keytar@*, keytar@^5.0.0: +keytar@*: version "5.0.0" resolved "https://registry.yarnpkg.com/keytar/-/keytar-5.0.0.tgz#c89b6b7a4608fd7af633d9f8474b1a7eb97cbe6f" integrity sha512-a5UheK59YOlJf9i+2Osaj/kkH6mK0RCHVMtJ84u6ZfbfRIbOJ/H4b5VlOF/LgNHF6s78dRSBzZnvIuPiBKv6wg== diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 20e6be79d62..abe883aa72e 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -75,8 +75,8 @@ suite('workspace-namespace', () => { }); }); - test('openTextDocument, untitled is dirty', function () { - return vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.workspaceFolders![0].uri.toString() || '', './newfile.txt'))).then(doc => { + test('openTextDocument, untitled is dirty', async function () { + return vscode.workspace.openTextDocument(vscode.workspace.workspaceFolders![0].uri.with({ scheme: 'untitled', path: posix.join(vscode.workspace.workspaceFolders![0].uri.path, 'newfile.txt') })).then(doc => { assert.equal(doc.uri.scheme, 'untitled'); assert.ok(doc.isDirty); }); diff --git a/extensions/yarn.lock b/extensions/yarn.lock index a8eb51df657..dc1fa7c2de8 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.7.3: - version "3.7.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" - integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== +typescript@3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== diff --git a/package.json b/package.json index c0cdd440cd1..f13416fb9a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "3171dfd018976ad5d8248fb4e3b712c439788be9", + "distro": "17f41b9e0d67fc6e53bd63a5f199a0f89ee70e1b", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 231180d513f..a68e020f9f1 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -12,47 +12,47 @@ export namespace Schemas { * A schema that is used for models that exist in memory * only and that have no correspondence on a server or such. */ - export const inMemory: string = 'inmemory'; + export const inMemory = 'inmemory'; /** * A schema that is used for setting files */ - export const vscode: string = 'vscode'; + export const vscode = 'vscode'; /** * A schema that is used for internal private files */ - export const internal: string = 'private'; + export const internal = 'private'; /** * A walk-through document. */ - export const walkThrough: string = 'walkThrough'; + export const walkThrough = 'walkThrough'; /** * An embedded code snippet. */ - export const walkThroughSnippet: string = 'walkThroughSnippet'; + export const walkThroughSnippet = 'walkThroughSnippet'; - export const http: string = 'http'; + export const http = 'http'; - export const https: string = 'https'; + export const https = 'https'; - export const file: string = 'file'; + export const file = 'file'; - export const mailto: string = 'mailto'; + export const mailto = 'mailto'; - export const untitled: string = 'untitled'; + export const untitled = 'untitled'; - export const data: string = 'data'; + export const data = 'data'; - export const command: string = 'command'; + export const command = 'command'; - export const vscodeRemote: string = 'vscode-remote'; + export const vscodeRemote = 'vscode-remote'; - export const vscodeRemoteResource: string = 'vscode-remote-resource'; + export const vscodeRemoteResource = 'vscode-remote-resource'; - export const userData: string = 'vscode-userdata'; + export const userData = 'vscode-userdata'; } class RemoteAuthoritiesImpl { diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index af00dd94a84..1f1c08a58f1 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -648,7 +648,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (windowConfig?.autoDetectHighContrast === false) { autoDetectHighContrast = false; } - windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); + windowConfiguration.highContrast = isWindows && autoDetectHighContrast && nativeTheme.shouldUseInvertedColorScheme; windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 6ce7362aacc..3ee18add0e9 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -103,7 +103,7 @@ export class TextAreaHandler extends ViewPart { this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; - this._contentHeight = layoutInfo.contentHeight; + this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); @@ -354,7 +354,7 @@ export class TextAreaHandler extends ViewPart { this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; - this._contentHeight = layoutInfo.contentHeight; + this._contentHeight = layoutInfo.height; this._fontInfo = options.get(EditorOption.fontInfo); this._lineHeight = options.get(EditorOption.lineHeight); this._emptySelectionClipboard = options.get(EditorOption.emptySelectionClipboard); diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index bf098e0699f..5764ecd2ae6 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -490,6 +490,11 @@ export interface ICodeEditor extends editorCommon.IEditor { * @event */ onDidLayoutChange(listener: (e: EditorLayoutInfo) => void): IDisposable; + /** + * An event emitted when the content width or content height in the editor has changed. + * @event + */ + onDidContentSizeChange(listener: (e: editorCommon.IContentSizeChangedEvent) => void): IDisposable; /** * An event emitted when the scroll in the editor has changed. * @event @@ -566,6 +571,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ setValue(newValue: string): void; + /** + * Get the width of the editor's content. + * This is information that is "erased" when computing `scrollWidth = Math.max(contentWidth, width)` + */ + getContentWidth(): number; /** * Get the scrollWidth of the editor's viewport. */ @@ -575,6 +585,11 @@ export interface ICodeEditor extends editorCommon.IEditor { */ getScrollLeft(): number; + /** + * Get the height of the editor's content. + * This is information that is "erased" when computing `scrollHeight = Math.max(contentHeight, height)` + */ + getContentHeight(): number; /** * Get the scrollHeight of the editor's viewport. */ diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 1af4947456b..70582d0ebbf 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -308,6 +308,10 @@ export class View extends ViewEventHandler { this._applyLayout(); return false; } + public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean { + this.outgoingEvents.emitContentSizeChange(e); + return false; + } public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { this.domNode.setClassName(this.getEditorClassName()); this._context.model.setHasFocus(e.isFocused); diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewOutgoingEvents.ts index 8a39ae53519..6ba6ff3f123 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewOutgoingEvents.ts @@ -9,7 +9,7 @@ import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget'; import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { IScrollEvent } from 'vs/editor/common/editorCommon'; +import { IScrollEvent, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; @@ -20,6 +20,7 @@ export interface EventCallback { export class ViewOutgoingEvents extends Disposable { + public onDidContentSizeChange: EventCallback | null = null; public onDidScroll: EventCallback | null = null; public onDidGainFocus: EventCallback | null = null; public onDidLoseFocus: EventCallback | null = null; @@ -41,6 +42,12 @@ export class ViewOutgoingEvents extends Disposable { this._viewModel = viewModel; } + public emitContentSizeChange(e: viewEvents.ViewContentSizeChangedEvent): void { + if (this.onDidContentSizeChange) { + this.onDidContentSizeChange(e); + } + } + public emitScrollChanged(e: viewEvents.ViewScrollChangedEvent): void { if (this.onDidScroll) { this.onDidScroll(e); diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 11a01c1eb11..73a755d84d8 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -56,7 +56,7 @@ export class EditorScrollbar extends ViewPart { fastScrollSensitivity: fastScrollSensitivity, }; - this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.scrollable)); + this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable())); PartFingerprints.write(this.scrollbar.getDomNode(), PartFingerprint.ScrollableElement); this.scrollbarDomNode = createFastDomNode(this.scrollbar.getDomNode()); @@ -113,7 +113,7 @@ export class EditorScrollbar extends ViewPart { } else { this.scrollbarDomNode.setWidth(layoutInfo.contentWidth); } - this.scrollbarDomNode.setHeight(layoutInfo.contentHeight); + this.scrollbarDomNode.setHeight(layoutInfo.height); } public getOverviewRulerLayoutInfo(): IOverviewRulerLayoutInfo { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 2f8f5cf4f98..f81d9490fea 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -195,6 +195,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onKeyDown: Emitter = this._register(new Emitter()); public readonly onKeyDown: Event = this._onKeyDown.event; + private readonly _onDidContentSizeChange: Emitter = this._register(new Emitter()); + public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; + private readonly _onDidScrollChange: Emitter = this._register(new Emitter()); public readonly onDidScrollChange: Event = this._onDidScrollChange.event; @@ -759,6 +762,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.cursor.setSelections(source, ranges); } + public getContentWidth(): number { + if (!this._modelData) { + return -1; + } + return this._modelData.viewModel.viewLayout.getContentWidth(); + } + public getScrollWidth(): number { if (!this._modelData) { return -1; @@ -772,6 +782,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return this._modelData.viewModel.viewLayout.getCurrentScrollLeft(); } + public getContentHeight(): number { + if (!this._modelData) { + return -1; + } + return this._modelData.viewModel.viewLayout.getContentHeight(); + } + public getScrollHeight(): number { if (!this._modelData) { return -1; @@ -1479,6 +1496,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); + viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e); viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true); viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 533b40bcd45..573a40f5c88 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1124,11 +1124,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE let scrollTop = this.modifiedEditor.getScrollTop(); let scrollHeight = this.modifiedEditor.getScrollHeight(); - let computedAvailableSize = Math.max(0, layoutInfo.contentHeight); + let computedAvailableSize = Math.max(0, layoutInfo.height); let computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0); let computedRatio = scrollHeight > 0 ? (computedRepresentableSize / scrollHeight) : 0; - let computedSliderSize = Math.max(0, Math.floor(layoutInfo.contentHeight * computedRatio)); + let computedSliderSize = Math.max(0, Math.floor(layoutInfo.height * computedRatio)); let computedSliderPosition = Math.floor(scrollTop * computedRatio); return { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 6304bfb3754..d7fa2b25e1c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -1632,10 +1632,6 @@ export interface EditorLayoutInfo { * The width of the glyph margin. */ readonly glyphMarginWidth: number; - /** - * The height of the glyph margin. - */ - readonly glyphMarginHeight: number; /** * Left position for the line numbers. @@ -1645,10 +1641,6 @@ export interface EditorLayoutInfo { * The width of the line numbers. */ readonly lineNumbersWidth: number; - /** - * The height of the line numbers. - */ - readonly lineNumbersHeight: number; /** * Left position for the line decorations. @@ -1658,10 +1650,6 @@ export interface EditorLayoutInfo { * The width of the line decorations. */ readonly decorationsWidth: number; - /** - * The height of the line decorations. - */ - readonly decorationsHeight: number; /** * Left position for the content (actual text) @@ -1671,10 +1659,6 @@ export interface EditorLayoutInfo { * The width of the content (actual text) */ readonly contentWidth: number; - /** - * The height of the content (actual height) - */ - readonly contentHeight: number; /** * The position for the minimap @@ -1861,19 +1845,15 @@ export class EditorLayoutInfoComputer extends ComputedEditorOptionthing).newUri) || Boolean((thing).oldUri)); +export namespace WorkspaceFileEdit { + /** + * @internal + */ + export function is(thing: any): thing is WorkspaceFileEdit { + return isObject(thing) && (Boolean((thing).newUri) || Boolean((thing).oldUri)); + } } /** * @internal */ -export function isResourceTextEdit(thing: any): thing is ResourceTextEdit { - return isObject(thing) && (thing).resource && Array.isArray((thing).edits); +export namespace WorkspaceTextEdit { + /** + * @internal + */ + export function is(thing: any): thing is WorkspaceTextEdit { + return isObject(thing) && (thing).resource && Array.isArray((thing).edits); + } } -export interface ResourceFileEdit { +export interface WorkspaceFileEdit { oldUri?: URI; newUri?: URI; options?: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean }; } -export interface ResourceTextEdit { +export interface WorkspaceTextEdit { resource: URI; modelVersionId?: number; edits: TextEdit[]; } export interface WorkspaceEdit { - edits: Array; + edits: Array; } export interface Rejection { diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index c82cf9ac360..70f8ee7fec9 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -9,25 +9,26 @@ import { ScrollEvent } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { ScrollType } from 'vs/editor/common/editorCommon'; +import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; export const enum ViewEventType { ViewConfigurationChanged = 1, - ViewCursorStateChanged = 2, - ViewDecorationsChanged = 3, - ViewFlushed = 4, - ViewFocusChanged = 5, - ViewLineMappingChanged = 6, - ViewLinesChanged = 7, - ViewLinesDeleted = 8, - ViewLinesInserted = 9, - ViewRevealRangeRequest = 10, - ViewScrollChanged = 11, - ViewTokensChanged = 12, - ViewTokensColorsChanged = 13, - ViewZonesChanged = 14, - ViewThemeChanged = 15, - ViewLanguageConfigurationChanged = 16 + ViewContentSizeChanged = 2, + ViewCursorStateChanged = 3, + ViewDecorationsChanged = 4, + ViewFlushed = 5, + ViewFocusChanged = 6, + ViewLanguageConfigurationChanged = 7, + ViewLineMappingChanged = 8, + ViewLinesChanged = 9, + ViewLinesDeleted = 10, + ViewLinesInserted = 11, + ViewRevealRangeRequest = 12, + ViewScrollChanged = 13, + ViewThemeChanged = 14, + ViewTokensChanged = 15, + ViewTokensColorsChanged = 16, + ViewZonesChanged = 17, } export class ViewConfigurationChangedEvent { @@ -45,6 +46,25 @@ export class ViewConfigurationChangedEvent { } } +export class ViewContentSizeChangedEvent implements IContentSizeChangedEvent { + + public readonly type = ViewEventType.ViewContentSizeChanged; + + public readonly contentWidth: number; + public readonly contentHeight: number; + + public readonly contentWidthChanged: boolean; + public readonly contentHeightChanged: boolean; + + constructor(source: IContentSizeChangedEvent) { + this.contentWidth = source.contentWidth; + this.contentHeight = source.contentHeight; + + this.contentWidthChanged = source.contentWidthChanged; + this.contentHeightChanged = source.contentHeightChanged; + } +} + export class ViewCursorStateChangedEvent { public readonly type = ViewEventType.ViewCursorStateChanged; @@ -88,6 +108,11 @@ export class ViewFocusChangedEvent { } } +export class ViewLanguageConfigurationEvent { + + public readonly type = ViewEventType.ViewLanguageConfigurationChanged; +} + export class ViewLineMappingChangedEvent { public readonly type = ViewEventType.ViewLineMappingChanged; @@ -221,6 +246,11 @@ export class ViewScrollChangedEvent { } } +export class ViewThemeChangedEvent { + + public readonly type = ViewEventType.ViewThemeChanged; +} + export class ViewTokensChangedEvent { public readonly type = ViewEventType.ViewTokensChanged; @@ -241,11 +271,6 @@ export class ViewTokensChangedEvent { } } -export class ViewThemeChangedEvent { - - public readonly type = ViewEventType.ViewThemeChanged; -} - export class ViewTokensColorsChangedEvent { public readonly type = ViewEventType.ViewTokensColorsChanged; @@ -264,28 +289,24 @@ export class ViewZonesChangedEvent { } } -export class ViewLanguageConfigurationEvent { - - public readonly type = ViewEventType.ViewLanguageConfigurationChanged; -} - export type ViewEvent = ( ViewConfigurationChangedEvent + | ViewContentSizeChangedEvent | ViewCursorStateChangedEvent | ViewDecorationsChangedEvent | ViewFlushedEvent | ViewFocusChangedEvent - | ViewLinesChangedEvent + | ViewLanguageConfigurationEvent | ViewLineMappingChangedEvent + | ViewLinesChangedEvent | ViewLinesDeletedEvent | ViewLinesInsertedEvent | ViewRevealRangeRequestEvent | ViewScrollChangedEvent + | ViewThemeChangedEvent | ViewTokensChangedEvent | ViewTokensColorsChangedEvent | ViewZonesChangedEvent - | ViewThemeChangedEvent - | ViewLanguageConfigurationEvent ); export interface IViewEventListener { diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 6ab6d10098b..31148f59ab0 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -3,24 +3,157 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { IConfiguration } from 'vs/editor/common/editorCommon'; +import { IConfiguration, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon'; import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel'; const SMOOTH_SCROLLING_TIME = 125; +class EditorScrollDimensions { + + public readonly width: number; + public readonly contentWidth: number; + public readonly scrollWidth: number; + + public readonly height: number; + public readonly contentHeight: number; + public readonly scrollHeight: number; + + constructor( + width: number, + contentWidth: number, + height: number, + contentHeight: number, + ) { + width = width | 0; + contentWidth = contentWidth | 0; + height = height | 0; + contentHeight = contentHeight | 0; + + if (width < 0) { + width = 0; + } + if (contentWidth < 0) { + contentWidth = 0; + } + + if (height < 0) { + height = 0; + } + if (contentHeight < 0) { + contentHeight = 0; + } + + this.width = width; + this.contentWidth = contentWidth; + this.scrollWidth = Math.max(width, contentWidth); + + this.height = height; + this.contentHeight = contentHeight; + this.scrollHeight = Math.max(height, contentHeight); + } + + public equals(other: EditorScrollDimensions): boolean { + return ( + this.width === other.width + && this.contentWidth === other.contentWidth + && this.height === other.height + && this.contentHeight === other.contentHeight + ); + } +} + +class EditorScrollable extends Disposable { + + private readonly _scrollable: Scrollable; + private _dimensions: EditorScrollDimensions; + + public readonly onDidScroll: Event; + + private readonly _onDidContentSizeChange = this._register(new Emitter()); + public readonly onDidContentSizeChange: Event = this._onDidContentSizeChange.event; + + constructor(smoothScrollDuration: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { + super(); + this._dimensions = new EditorScrollDimensions(0, 0, 0, 0); + this._scrollable = this._register(new Scrollable(smoothScrollDuration, scheduleAtNextAnimationFrame)); + this.onDidScroll = this._scrollable.onScroll; + } + + public getScrollable(): Scrollable { + return this._scrollable; + } + + public setSmoothScrollDuration(smoothScrollDuration: number): void { + this._scrollable.setSmoothScrollDuration(smoothScrollDuration); + } + + public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition { + return this._scrollable.validateScrollPosition(scrollPosition); + } + + public getScrollDimensions(): EditorScrollDimensions { + return this._dimensions; + } + + public setScrollDimensions(dimensions: EditorScrollDimensions): void { + if (this._dimensions.equals(dimensions)) { + return; + } + + const oldDimensions = this._dimensions; + this._dimensions = dimensions; + + this._scrollable.setScrollDimensions({ + width: dimensions.width, + scrollWidth: dimensions.scrollWidth, + height: dimensions.height, + scrollHeight: dimensions.scrollHeight + }); + + const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth); + const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight); + if (contentWidthChanged || contentHeightChanged) { + this._onDidContentSizeChange.fire({ + contentWidth: dimensions.contentWidth, + contentHeight: dimensions.contentHeight, + + contentWidthChanged: contentWidthChanged, + contentHeightChanged: contentHeightChanged + }); + } + } + + public getFutureScrollPosition(): IScrollPosition { + return this._scrollable.getFutureScrollPosition(); + } + + public getCurrentScrollPosition(): IScrollPosition { + return this._scrollable.getCurrentScrollPosition(); + } + + public setScrollPositionNow(update: INewScrollPosition): void { + this._scrollable.setScrollPositionNow(update); + } + + public setScrollPositionSmooth(update: INewScrollPosition): void { + this._scrollable.setScrollPositionSmooth(update); + } +} + export class ViewLayout extends Disposable implements IViewLayout { private readonly _configuration: IConfiguration; private readonly _linesLayout: LinesLayout; - public readonly scrollable: Scrollable; + private readonly _scrollable: EditorScrollable; public readonly onDidScroll: Event; + public readonly onDidContentSizeChange: Event; constructor(configuration: IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { super(); @@ -31,14 +164,17 @@ export class ViewLayout extends Disposable implements IViewLayout { this._linesLayout = new LinesLayout(lineCount, options.get(EditorOption.lineHeight)); - this.scrollable = this._register(new Scrollable(0, scheduleAtNextAnimationFrame)); + this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame)); this._configureSmoothScrollDuration(); - this.scrollable.setScrollDimensions({ - width: layoutInfo.contentWidth, - height: layoutInfo.contentHeight - }); - this.onDidScroll = this.scrollable.onScroll; + this._scrollable.setScrollDimensions(new EditorScrollDimensions( + layoutInfo.contentWidth, + 0, + layoutInfo.height, + 0 + )); + this.onDidScroll = this._scrollable.onDidScroll; + this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange; this._updateHeight(); } @@ -47,12 +183,16 @@ export class ViewLayout extends Disposable implements IViewLayout { super.dispose(); } + public getScrollable(): Scrollable { + return this._scrollable.getScrollable(); + } + public onHeightMaybeChanged(): void { this._updateHeight(); } private _configureSmoothScrollDuration(): void { - this.scrollable.setSmoothScrollDuration(this._configuration.options.get(EditorOption.smoothScrolling) ? SMOOTH_SCROLLING_TIME : 0); + this._scrollable.setSmoothScrollDuration(this._configuration.options.get(EditorOption.smoothScrolling) ? SMOOTH_SCROLLING_TIME : 0); } // ---- begin view event handlers @@ -65,16 +205,15 @@ export class ViewLayout extends Disposable implements IViewLayout { if (e.hasChanged(EditorOption.layoutInfo)) { const layoutInfo = options.get(EditorOption.layoutInfo); const width = layoutInfo.contentWidth; - const height = layoutInfo.contentHeight; - const scrollDimensions = this.scrollable.getScrollDimensions(); + const height = layoutInfo.height; + const scrollDimensions = this._scrollable.getScrollDimensions(); const scrollWidth = scrollDimensions.scrollWidth; - const scrollHeight = this._getTotalHeight(width, height, scrollWidth); - - this.scrollable.setScrollDimensions({ - width: width, - height: height, - scrollHeight: scrollHeight - }); + this._scrollable.setScrollDimensions(new EditorScrollDimensions( + width, + scrollDimensions.contentWidth, + height, + this._getContentHeight(width, height, scrollWidth) + )); } else { this._updateHeight(); } @@ -108,7 +247,7 @@ export class ViewLayout extends Disposable implements IViewLayout { return scrollbar.horizontalScrollbarSize; } - private _getTotalHeight(width: number, height: number, scrollWidth: number): number { + private _getContentHeight(width: number, height: number, scrollWidth: number): number { const options = this._configuration.options; let result = this._linesLayout.getLinesTotalHeight(); @@ -118,25 +257,27 @@ export class ViewLayout extends Disposable implements IViewLayout { result += this._getHorizontalScrollbarHeight(width, scrollWidth); } - return Math.max(height, result); + return result; } private _updateHeight(): void { - const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollDimensions = this._scrollable.getScrollDimensions(); const width = scrollDimensions.width; const height = scrollDimensions.height; const scrollWidth = scrollDimensions.scrollWidth; - const scrollHeight = this._getTotalHeight(width, height, scrollWidth); - this.scrollable.setScrollDimensions({ - scrollHeight: scrollHeight - }); + this._scrollable.setScrollDimensions(new EditorScrollDimensions( + width, + scrollDimensions.contentWidth, + height, + this._getContentHeight(width, height, scrollWidth) + )); } // ---- Layouting logic public getCurrentViewport(): Viewport { - const scrollDimensions = this.scrollable.getScrollDimensions(); - const currentScrollPosition = this.scrollable.getCurrentScrollPosition(); + const scrollDimensions = this._scrollable.getScrollDimensions(); + const currentScrollPosition = this._scrollable.getCurrentScrollPosition(); return new Viewport( currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, @@ -146,8 +287,8 @@ export class ViewLayout extends Disposable implements IViewLayout { } public getFutureViewport(): Viewport { - const scrollDimensions = this.scrollable.getScrollDimensions(); - const currentScrollPosition = this.scrollable.getFutureScrollPosition(); + const scrollDimensions = this._scrollable.getScrollDimensions(); + const currentScrollPosition = this._scrollable.getFutureScrollPosition(); return new Viewport( currentScrollPosition.scrollTop, currentScrollPosition.scrollLeft, @@ -156,23 +297,27 @@ export class ViewLayout extends Disposable implements IViewLayout { ); } - private _computeScrollWidth(maxLineWidth: number, viewportWidth: number): number { + private _computeContentWidth(maxLineWidth: number): number { const options = this._configuration.options; const wrappingInfo = options.get(EditorOption.wrappingInfo); let isViewportWrapping = wrappingInfo.isViewportWrapping; if (!isViewportWrapping) { const extraHorizontalSpace = options.get(EditorOption.scrollBeyondLastColumn) * options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth(); - return Math.max(maxLineWidth + extraHorizontalSpace, viewportWidth, whitespaceMinWidth); + return Math.max(maxLineWidth + extraHorizontalSpace, whitespaceMinWidth); } - return Math.max(maxLineWidth, viewportWidth); + return maxLineWidth; } public onMaxLineWidthChanged(maxLineWidth: number): void { - let newScrollWidth = this._computeScrollWidth(maxLineWidth, this.getCurrentViewport().width); - this.scrollable.setScrollDimensions({ - scrollWidth: newScrollWidth - }); + const scrollDimensions = this._scrollable.getScrollDimensions(); + // const newScrollWidth = ; + this._scrollable.setScrollDimensions(new EditorScrollDimensions( + scrollDimensions.width, + this._computeContentWidth(maxLineWidth), + scrollDimensions.height, + scrollDimensions.contentHeight + )); // The height might depend on the fact that there is a horizontal scrollbar or not this._updateHeight(); @@ -181,7 +326,7 @@ export class ViewLayout extends Disposable implements IViewLayout { // ---- view state public saveState(): { scrollTop: number; scrollTopWithoutViewZones: number; scrollLeft: number; } { - const currentScrollPosition = this.scrollable.getFutureScrollPosition(); + const currentScrollPosition = this._scrollable.getFutureScrollPosition(); let scrollTop = currentScrollPosition.scrollTop; let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop); let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport); @@ -215,7 +360,7 @@ export class ViewLayout extends Disposable implements IViewLayout { } public getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData { // do some minimal validations on scrollTop - const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollDimensions = this._scrollable.getScrollDimensions(); if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) { scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height; } @@ -234,40 +379,47 @@ export class ViewLayout extends Disposable implements IViewLayout { // ---- IScrollingProvider - + public getContentWidth(): number { + const scrollDimensions = this._scrollable.getScrollDimensions(); + return scrollDimensions.contentWidth; + } public getScrollWidth(): number { - const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollDimensions = this._scrollable.getScrollDimensions(); return scrollDimensions.scrollWidth; } + public getContentHeight(): number { + const scrollDimensions = this._scrollable.getScrollDimensions(); + return scrollDimensions.contentHeight; + } public getScrollHeight(): number { - const scrollDimensions = this.scrollable.getScrollDimensions(); + const scrollDimensions = this._scrollable.getScrollDimensions(); return scrollDimensions.scrollHeight; } public getCurrentScrollLeft(): number { - const currentScrollPosition = this.scrollable.getCurrentScrollPosition(); + const currentScrollPosition = this._scrollable.getCurrentScrollPosition(); return currentScrollPosition.scrollLeft; } public getCurrentScrollTop(): number { - const currentScrollPosition = this.scrollable.getCurrentScrollPosition(); + const currentScrollPosition = this._scrollable.getCurrentScrollPosition(); return currentScrollPosition.scrollTop; } public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition { - return this.scrollable.validateScrollPosition(scrollPosition); + return this._scrollable.validateScrollPosition(scrollPosition); } public setScrollPositionNow(position: INewScrollPosition): void { - this.scrollable.setScrollPositionNow(position); + this._scrollable.setScrollPositionNow(position); } public setScrollPositionSmooth(position: INewScrollPosition): void { - this.scrollable.setScrollPositionSmooth(position); + this._scrollable.setScrollPositionSmooth(position); } public deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void { - const currentScrollPosition = this.scrollable.getCurrentScrollPosition(); - this.scrollable.setScrollPositionNow({ + const currentScrollPosition = this._scrollable.getCurrentScrollPosition(); + this._scrollable.setScrollPositionNow({ scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft, scrollTop: currentScrollPosition.scrollTop + deltaScrollTop }); diff --git a/src/vs/editor/common/viewModel/viewEventHandler.ts b/src/vs/editor/common/viewModel/viewEventHandler.ts index e421f5ffde2..b8d0bc823a6 100644 --- a/src/vs/editor/common/viewModel/viewEventHandler.ts +++ b/src/vs/editor/common/viewModel/viewEventHandler.ts @@ -36,6 +36,9 @@ export class ViewEventHandler extends Disposable { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { return false; } + public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean { + return false; + } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return false; } @@ -69,6 +72,9 @@ export class ViewEventHandler extends Disposable { public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { return false; } + public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { + return false; + } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { return false; } @@ -78,9 +84,6 @@ export class ViewEventHandler extends Disposable { public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { return false; } - public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean { - return false; - } // --- end event handlers @@ -99,6 +102,12 @@ export class ViewEventHandler extends Disposable { } break; + case viewEvents.ViewEventType.ViewContentSizeChanged: + if (this.onContentSizeChanged(e)) { + shouldRender = true; + } + break; + case viewEvents.ViewEventType.ViewCursorStateChanged: if (this.onCursorStateChanged(e)) { shouldRender = true; @@ -171,6 +180,12 @@ export class ViewEventHandler extends Disposable { } break; + case viewEvents.ViewEventType.ViewThemeChanged: + if (this.onThemeChanged(e)) { + shouldRender = true; + } + break; + case viewEvents.ViewEventType.ViewTokensColorsChanged: if (this.onTokensColorsChanged(e)) { shouldRender = true; @@ -183,12 +198,6 @@ export class ViewEventHandler extends Disposable { } break; - case viewEvents.ViewEventType.ViewThemeChanged: - if (this.onThemeChanged(e)) { - shouldRender = true; - } - break; - default: console.info('View received unknown event: '); console.info(e); diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index a79c030bf96..ebf710f7214 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -41,7 +41,7 @@ export class Viewport { export interface IViewLayout { - readonly scrollable: Scrollable; + getScrollable(): Scrollable; onMaxLineWidthChanged(width: number): void; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 75842518ee9..665b657ddb2 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -100,6 +100,15 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel } })); + this._register(this.viewLayout.onDidContentSizeChange((e) => { + try { + const eventsCollector = this._beginEmit(); + eventsCollector.emit(new viewEvents.ViewContentSizeChangedEvent(e)); + } finally { + this._endEmit(); + } + })); + this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter); this._registerModelEvents(); diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 8a829bff0ca..f65118eb388 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -69,7 +69,7 @@ suite('CodeAction', () => { bcd: { diagnostics: [], edit: new class implements modes.WorkspaceEdit { - edits!: modes.ResourceTextEdit[]; + edits!: modes.WorkspaceTextEdit[]; }, title: 'abc' } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 9f3c10786f6..8621568f2a2 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -31,6 +31,9 @@ import { IdleValue, raceCancellation } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, ConfigurationScope, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; class RenameSkeleton { @@ -115,6 +118,7 @@ class RenameController implements IEditorContribution { @IBulkEditService private readonly _bulkEditService: IBulkEditService, @IEditorProgressService private readonly _progressService: IEditorProgressService, @ILogService private readonly _logService: ILogService, + @ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService, ) { this._renameInputField = this._dispoableStore.add(new IdleValue(() => this._dispoableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])))); } @@ -175,7 +179,8 @@ class RenameController implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } - const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd); + const supportPreview = this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); + const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { @@ -299,7 +304,7 @@ registerEditorCommand(new RenameCommand({ registerEditorCommand(new RenameCommand({ id: 'acceptRenameInputWithPreview', - precondition: CONTEXT_RENAME_INPUT_VISIBLE, + precondition: ContextKeyExpr.and(CONTEXT_RENAME_INPUT_VISIBLE, ContextKeyExpr.has('config.editor.rename.enablePreview')), handler: x => x.acceptRenameInput(true), kbOpts: { weight: KeybindingWeight.EditorContrib + 99, @@ -329,3 +334,17 @@ registerDefaultLanguageCommand('_executeDocumentRenameProvider', function (model } return rename(model, position, newName); }); + + +//todo@joh use editor options world +Registry.as(Extensions.Configuration).registerConfiguration({ + id: 'editor', + properties: { + 'editor.rename.enablePreview': { + scope: ConfigurationScope.RESOURCE_LANGUAGE, + description: nls.localize('enablePreview', "Enabe/disable the ability to preview changes before renaming"), + default: true, + type: 'boolean' + } + } +}); diff --git a/src/vs/editor/contrib/rename/renameInputField.css b/src/vs/editor/contrib/rename/renameInputField.css index 1a6d7a75ec0..f8473863b15 100644 --- a/src/vs/editor/contrib/rename/renameInputField.css +++ b/src/vs/editor/contrib/rename/renameInputField.css @@ -6,6 +6,9 @@ .monaco-editor .rename-box { z-index: 100; color: inherit; +} + +.monaco-editor .rename-box.preview { padding: 4px; } @@ -15,5 +18,10 @@ } .monaco-editor .rename-box .rename-label { + display: none; opacity: .8; } + +.monaco-editor .rename-box.preview .rename-label { + display: inherit; +} diff --git a/src/vs/editor/contrib/rename/renameInputField.ts b/src/vs/editor/contrib/rename/renameInputField.ts index dd036ba5df6..1a3e60e0071 100644 --- a/src/vs/editor/contrib/rename/renameInputField.ts +++ b/src/vs/editor/contrib/rename/renameInputField.ts @@ -15,6 +15,7 @@ import { inputBackground, inputBorder, inputForeground, widgetShadow, editorWidg import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { toggleClass } from 'vs/base/browser/dom'; export const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); @@ -148,7 +149,9 @@ export class RenameInputField implements IContentWidget { } } - getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number): Promise { + getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean): Promise { + + toggleClass(this._domNode!, 'preview', supportPreview); this._position = new Position(where.startLineNumber, where.startColumn); this._input!.value = value; @@ -178,7 +181,7 @@ export class RenameInputField implements IContentWidget { this._currentCancelInput = undefined; resolve({ newName: this._input!.value, - wantsPreview + wantsPreview: supportPreview && wantsPreview }); }; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 2f2419ca6a8..ebc82bae46a 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -20,7 +20,7 @@ import { IPosition, Position as Pos } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditor } from 'vs/editor/common/editorCommon'; import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; -import { TextEdit, WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; +import { TextEdit, WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService, ITextResourceConfigurationChangeEvent } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -645,7 +645,7 @@ export class SimpleBulkEditService implements IBulkEditService { if (workspaceEdit.edits) { for (let edit of workspaceEdit.edits) { - if (!isResourceTextEdit(edit)) { + if (!WorkspaceTextEdit.is(edit)) { return Promise.reject(new Error('bad edit - only text edits are supported')); } let model = this._modelService.getModel(edit.resource); diff --git a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts index 8e71814c2aa..cfb8396fe37 100644 --- a/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts @@ -112,19 +112,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 990, - contentHeight: 800, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -170,19 +166,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 990, - contentHeight: 800, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -228,19 +220,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 890, - contentHeight: 800, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -286,19 +274,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 900, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 10, contentWidth: 890, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -344,19 +328,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 900, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 10, contentWidth: 890, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -402,19 +382,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 50, - lineNumbersHeight: 900, decorationsLeft: 50, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 60, contentWidth: 840, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -460,19 +436,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 50, - lineNumbersHeight: 900, decorationsLeft: 50, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 60, contentWidth: 840, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -518,19 +490,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 60, - lineNumbersHeight: 900, decorationsLeft: 60, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 70, contentWidth: 830, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -576,19 +544,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 30, - lineNumbersHeight: 900, decorationsLeft: 30, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 40, contentWidth: 860, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -634,19 +598,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 900, lineNumbersLeft: 0, lineNumbersWidth: 30, - lineNumbersHeight: 900, decorationsLeft: 30, decorationsWidth: 10, - decorationsHeight: 900, contentLeft: 40, contentWidth: 860, - contentHeight: 900, renderMinimap: RenderMinimap.None, minimapLeft: 0, @@ -692,19 +652,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 893, - contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 903, @@ -750,19 +706,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 893, - contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 903, @@ -808,19 +760,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 0, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 0, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 10, contentWidth: 935, - contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 945, @@ -866,19 +814,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 55, glyphMarginWidth: 0, - glyphMarginHeight: 800, lineNumbersLeft: 55, lineNumbersWidth: 0, - lineNumbersHeight: 800, decorationsLeft: 55, decorationsWidth: 10, - decorationsHeight: 800, contentLeft: 65, contentWidth: 935, - contentHeight: 800, renderMinimap: RenderMinimap.Text, minimapLeft: 0, @@ -924,19 +868,15 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 30, - glyphMarginHeight: 422, lineNumbersLeft: 30, lineNumbersWidth: 36, - lineNumbersHeight: 422, decorationsLeft: 66, decorationsWidth: 26, - decorationsHeight: 422, contentLeft: 92, contentWidth: 1018, - contentHeight: 422, renderMinimap: RenderMinimap.Text, minimapLeft: 1096, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index b1f6cf15b9a..12b602dbae4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2022,6 +2022,13 @@ declare namespace monaco.editor { readonly charChanges: ICharChange[] | undefined; } + export interface IContentSizeChangedEvent { + readonly contentWidth: number; + readonly contentHeight: number; + readonly contentWidthChanged: boolean; + readonly contentHeightChanged: boolean; + } + export interface INewScrollPosition { scrollLeft?: number; scrollTop?: number; @@ -3257,10 +3264,6 @@ declare namespace monaco.editor { * The width of the glyph margin. */ readonly glyphMarginWidth: number; - /** - * The height of the glyph margin. - */ - readonly glyphMarginHeight: number; /** * Left position for the line numbers. */ @@ -3269,10 +3272,6 @@ declare namespace monaco.editor { * The width of the line numbers. */ readonly lineNumbersWidth: number; - /** - * The height of the line numbers. - */ - readonly lineNumbersHeight: number; /** * Left position for the line decorations. */ @@ -3281,10 +3280,6 @@ declare namespace monaco.editor { * The width of the line decorations. */ readonly decorationsWidth: number; - /** - * The height of the line decorations. - */ - readonly decorationsHeight: number; /** * Left position for the content (actual text) */ @@ -3293,10 +3288,6 @@ declare namespace monaco.editor { * The width of the content (actual text) */ readonly contentWidth: number; - /** - * The height of the content (actual height) - */ - readonly contentHeight: number; /** * The position for the minimap */ @@ -4316,6 +4307,11 @@ declare namespace monaco.editor { * @event */ onDidLayoutChange(listener: (e: EditorLayoutInfo) => void): IDisposable; + /** + * An event emitted when the content width or content height in the editor has changed. + * @event + */ + onDidContentSizeChange(listener: (e: IContentSizeChangedEvent) => void): IDisposable; /** * An event emitted when the scroll in the editor has changed. * @event @@ -4377,6 +4373,11 @@ declare namespace monaco.editor { * @see `ITextModel.setValue` */ setValue(newValue: string): void; + /** + * Get the width of the editor's content. + * This is information that is "erased" when computing `scrollWidth = Math.max(contentWidth, width)` + */ + getContentWidth(): number; /** * Get the scrollWidth of the editor's viewport. */ @@ -4385,6 +4386,11 @@ declare namespace monaco.editor { * Get the scrollLeft of the editor's viewport. */ getScrollLeft(): number; + /** + * Get the height of the editor's content. + * This is information that is "erased" when computing `scrollHeight = Math.max(contentHeight, height)` + */ + getContentHeight(): number; /** * Get the scrollHeight of the editor's viewport. */ @@ -5868,7 +5874,7 @@ declare namespace monaco.languages { constructor(value: string); } - export interface ResourceFileEdit { + export interface WorkspaceFileEdit { oldUri?: Uri; newUri?: Uri; options?: { @@ -5879,14 +5885,14 @@ declare namespace monaco.languages { }; } - export interface ResourceTextEdit { + export interface WorkspaceTextEdit { resource: Uri; modelVersionId?: number; edits: TextEdit[]; } export interface WorkspaceEdit { - edits: Array; + edits: Array; } export interface Rejection { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 47ee6ba4c61..ece49da3e7c 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -232,7 +232,7 @@ export interface IFileDialogService { /** * Shows a save file dialog and save the file at the chosen file URI. */ - pickFileToSave(options: ISaveDialogOptions): Promise; + pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise; /** * Shows a save file dialog and returns the chosen file URI. diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index d2015ad7933..d2ea2f048a6 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -100,6 +100,10 @@ export class NsfwWatcherService implements IWatcherService { } } + if (this._verboseLogging) { + this.log(`Start watching with nsfw: ${request.path}`); + } + nsfw(request.path, events => { for (const e of events) { // Logging diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index c91a6a829c9..0ad9904baf2 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -105,15 +105,9 @@ export class ChokidarWatcherService implements IWatcherService { } private _watch(basePath: string, requests: IWatcherRequest[]): IWatcher { - if (this._verboseLogging) { - this.log(`Start watching: ${basePath}]`); - } const pollingInterval = this._pollingInterval || 5000; const usePolling = this._usePolling; - if (usePolling && this._verboseLogging) { - this.log(`Use polling instead of fs.watch: Polling interval ${pollingInterval} ms`); - } const watcherOpts: chokidar.WatchOptions = { ignoreInitial: true, @@ -155,6 +149,10 @@ export class ChokidarWatcherService implements IWatcherService { this.warn(`Watcher basePath does not match version on disk and was corrected (original: ${basePath}, real: ${realBasePath})`); } + if (this._verboseLogging) { + this.log(`Start watching with chockidar: ${realBasePath}, excludes: ${excludes.join(',')}, usePolling: ${usePolling ? 'true, interval ' + pollingInterval : 'false'}`); + } + let chokidarWatcher: chokidar.FSWatcher | null = chokidar.watch(realBasePath, watcherOpts); this._watcherCount++; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 9ccafb96921..e3048881b0c 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -38,7 +38,7 @@ export interface IQuickNavigateConfiguration { export interface IPickOptions { /** - * an optional string to show as place holder in the input box to guide the user what she picks on + * an optional string to show as placeholder in the input box to guide the user what she picks on */ placeHolder?: string; @@ -110,7 +110,7 @@ export interface IInputOptions { prompt?: string; /** - * an optional string to show as place holder in the input box to guide the user what to type + * an optional string to show as placeholder in the input box to guide the user what to type */ placeHolder?: string; @@ -131,6 +131,8 @@ export interface IQuickInput { title: string | undefined; + description: string | undefined; + step: number | undefined; totalSteps: number | undefined; diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index 8cd6ecdcaa7..6787d57bc27 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { systemPreferences, ipcMain as ipc } from 'electron'; +import { ipcMain as ipc, nativeTheme } from 'electron'; import { IStateService } from 'vs/platform/state/node/state'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -42,14 +42,14 @@ export class ThemeMainService implements IThemeMainService { } getBackgroundColor(): string { - if (isWindows && systemPreferences.isInvertedColorScheme()) { + if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { return DEFAULT_BG_HC_BLACK; } let background = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); if (!background) { let baseTheme: string; - if (isWindows && systemPreferences.isInvertedColorScheme()) { + if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { baseTheme = 'hc-black'; } else { baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts new file mode 100644 index 00000000000..c80ed504374 --- /dev/null +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService } from 'vs/platform/files/common/files'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { URI } from 'vs/base/common/uri'; +import { SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { joinPath } from 'vs/base/common/resources'; +import { toLocalISOString } from 'vs/base/common/date'; +import { ThrottledDelayer } from 'vs/base/common/async'; + +export abstract class AbstractSynchroniser extends Disposable { + + private readonly syncFolder: URI; + private cleanUpDelayer: ThrottledDelayer; + + constructor( + syncSource: SyncSource, + @IFileService protected readonly fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + super(); + this.syncFolder = joinPath(environmentService.userRoamingDataHome, '.sync', syncSource); + this.cleanUpDelayer = new ThrottledDelayer(50); + } + + protected async backupLocal(content: VSBuffer): Promise { + const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); + await this.fileService.writeFile(resource, content); + this.cleanUpDelayer.trigger(() => this.cleanUpBackup()); + } + + private async cleanUpBackup(): Promise { + const stat = await this.fileService.resolve(this.syncFolder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort(); + const toDelete = all.slice(0, Math.max(0, all.length - 9)); + await Promise.all(toDelete.map(stat => this.fileService.del(stat.resource))); + } + } + +} diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 37a089e6674..2b020058704 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; @@ -21,6 +20,7 @@ import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncContent { mac?: string; @@ -37,7 +37,7 @@ interface ISyncPreviewResult { readonly hasConflicts: boolean; } -export class KeybindingsSynchroniser extends Disposable implements ISynchroniser { +export class KeybindingsSynchroniser extends AbstractSynchroniser implements ISynchroniser { private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; @@ -57,11 +57,11 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, + @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, ) { - super(); + super(SyncSource.Keybindings, fileService, environmentService); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire())); @@ -380,6 +380,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { if (oldContent) { // file exists already + await this.backupLocal(oldContent.value); await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); } else { // file does not exist diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 553d8d28ed0..ae53f11c2c1 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -23,6 +22,7 @@ import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; +import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -32,7 +32,7 @@ interface ISyncPreviewResult { readonly conflicts: IConflictSetting[]; } -export class SettingsSynchroniser extends Disposable implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractSynchroniser implements ISettingsSyncService { _serviceBrand: any; @@ -56,14 +56,14 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer private readonly lastSyncSettingsResource: URI; constructor( - @IFileService private readonly fileService: IFileService, + @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(); + super(SyncSource.Settings, fileService, environmentService); this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json'); this._register(this.fileService.watch(dirname(this.environmentService.settingsResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire())); @@ -430,6 +430,7 @@ export class SettingsSynchroniser extends Disposable implements ISettingsSyncSer private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { if (oldContent) { // file exists already + await this.backupLocal(oldContent.value); await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); } else { // file does not exist diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index ca87b9be80c..4dbaf176ef2 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -13,7 +13,7 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron'; +import { ipcMain as ipc, screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -226,16 +226,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // React to HC color scheme changes (Windows) if (isWindows) { - const onHighContrastChange = () => { - if (systemPreferences.isInvertedColorScheme() || systemPreferences.isHighContrastColorScheme()) { + nativeTheme.on('updated', () => { + if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { this.sendToAll('vscode:enterHighContrast'); } else { this.sendToAll('vscode:leaveHighContrast'); } - }; - - systemPreferences.on('inverted-color-scheme-changed', () => onHighContrastChange()); - systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); + }); } // When a window looses focus, save all windows state. This allows to diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a2b3112bc80..82a627a207a 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1621,7 +1621,7 @@ declare module 'vscode' { matchOnDetail?: boolean; /** - * An optional string to show as place holder in the input box to guide the user what to pick on. + * An optional string to show as placeholder in the input box to guide the user what to pick on. */ placeHolder?: string; @@ -1647,7 +1647,7 @@ declare module 'vscode' { export interface WorkspaceFolderPickOptions { /** - * An optional string to show as place holder in the input box to guide the user what to pick on. + * An optional string to show as placeholder in the input box to guide the user what to pick on. */ placeHolder?: string; @@ -1795,7 +1795,7 @@ declare module 'vscode' { prompt?: string; /** - * An optional string to show as place holder in the input box to guide the user what to type. + * An optional string to show as placeholder in the input box to guide the user what to type. */ placeHolder?: string; @@ -9187,7 +9187,7 @@ declare module 'vscode' { value: string; /** - * A string to show as place holder in the input box to guide the user. + * A string to show as placeholder in the input box to guide the user. */ placeholder: string; } diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index cd22db56f9d..96eb4dee60e 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -211,16 +211,15 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { // don't create a new file ontop of an existing file return Promise.reject(new Error('file already exists')); }, err => { - return this._doCreateUntitled(uri).then(resource => !!resource); + return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined).then(resource => !!resource); }); } - private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { + private _doCreateUntitled(associatedResource?: URI, mode?: string, initialValue?: string): Promise { return this._textFileService.untitled.resolve({ - resource, + associatedResource, mode, - initialValue, - useResourcePath: Boolean(resource && resource.path) + initialValue }).then(model => { const resource = model.resource; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 1d07e45b7e1..cc85e214500 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -309,7 +309,7 @@ class DropOverlay extends Themable { // Open as untitled file with the provided contents const contents = VSBuffer.wrap(new Uint8Array(event.target.result)).toString(); - const untitledEditor = this.textFileService.untitled.create({ resource: proposedFilePath, initialValue: contents }); + const untitledEditor = this.textFileService.untitled.create({ associatedResource: proposedFilePath, initialValue: contents }); if (!targetGroup) { targetGroup = ensureTargetGroup(); diff --git a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css index 7d43a8518b8..1a5f4d35f6c 100644 --- a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css @@ -45,6 +45,10 @@ background-repeat: no-repeat; } +.quick-input-description { + margin: 6px; +} + .quick-input-header { display: flex; padding: 6px 6px 0px 6px; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index c4c5273352b..ab197d73eda 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -64,6 +64,7 @@ interface QuickInputUI { leftActionBar: ActionBar; titleBar: HTMLElement; title: HTMLElement; + description: HTMLElement; rightActionBar: ActionBar; checkAll: HTMLInputElement; filterContainer: HTMLElement; @@ -95,6 +96,7 @@ interface QuickInputUI { type Visibilities = { title?: boolean; + description?: boolean; checkAll?: boolean; inputBox?: boolean; visibleCount?: boolean; @@ -108,6 +110,7 @@ type Visibilities = { class QuickInput extends Disposable implements IQuickInput { private _title: string | undefined; + private _description: string | undefined; private _steps: number | undefined; private _totalSteps: number | undefined; protected visible = false; @@ -139,6 +142,15 @@ class QuickInput extends Disposable implements IQuickInput { this.update(); } + get description() { + return this._description; + } + + set description(description: string | undefined) { + this._description = description; + this.update(); + } + get step() { return this._steps; } @@ -244,6 +256,10 @@ class QuickInput extends Disposable implements IQuickInput { if (this.ui.title.textContent !== title) { this.ui.title.textContent = title; } + const description = this.getDescription(); + if (this.ui.description.textContent !== description) { + this.ui.description.textContent = description; + } if (this.busy && !this.busyDelay) { this.busyDelay = new TimeoutTimer(); this.busyDelay.setIfNotSet(() => { @@ -298,6 +314,10 @@ class QuickInput extends Disposable implements IQuickInput { return ''; } + private getDescription() { + return this.description || ''; + } + private getSteps() { if (this.step && this.totalSteps) { return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps); @@ -713,7 +733,7 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { return; } - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: this.ok, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, description: !!this.description, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; @@ -872,7 +892,7 @@ class InputBox extends QuickInput implements IInputBox { if (!this.visible) { return; } - this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); + this.ui.setVisibilities({ title: !!this.title || !!this.step, description: !!this.description || !!this.step, inputBox: true, message: true }); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; @@ -1037,6 +1057,8 @@ export class QuickInputService extends Component implements IQuickInputService { const rightActionBar = this._register(new ActionBar(titleBar)); rightActionBar.domNode.classList.add('quick-input-right-action-bar'); + const description = dom.append(container, $('.quick-input-description')); + const headerContainer = dom.append(container, $('.quick-input-header')); const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); @@ -1166,6 +1188,7 @@ export class QuickInputService extends Component implements IQuickInputService { leftActionBar, titleBar, title, + description, rightActionBar, checkAll, filterContainer, @@ -1377,6 +1400,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.setEnabled(true); ui.leftActionBar.clear(); ui.title.textContent = ''; + ui.description.textContent = ''; ui.rightActionBar.clear(); ui.checkAll.checked = false; // ui.inputBox.value = ''; Avoid triggering an event. @@ -1410,6 +1434,7 @@ export class QuickInputService extends Component implements IQuickInputService { private setVisibilities(visibilities: Visibilities) { const ui = this.getUI(); ui.title.style.display = visibilities.title ? '' : 'none'; + ui.description.style.display = visibilities.description ? '' : 'none'; ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 96ab993a64e..11dcaafc9da 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -889,13 +889,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { - let viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container); - if (!viewDescriptorCollectionItem) { - // Create and register the collection if does not exist - this.onDidRegisterViewContainer(container); - viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container); - } - return viewDescriptorCollectionItem!.viewDescriptorCollection; + return this.getOrRegisterViewDescriptorCollection(container); } moveView(view: IViewDescriptor, location: ViewContainerLocation): void { @@ -1024,22 +1018,28 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { - if (this.viewDescriptorCollections.has(viewContainer)) { - return; + this.getOrRegisterViewDescriptorCollection(viewContainer); + } + + private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection { + let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection; + + if (!viewDescriptorCollection) { + const disposables = new DisposableStore(); + viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); + + this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); + viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); + + this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); + + const viewsToRegister = this.getViewsByContainer(viewContainer); + if (viewsToRegister.length) { + this.addViews(viewContainer, viewsToRegister); + } } - const disposables = new DisposableStore(); - const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); - - this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); - viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); - - this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); - - const viewsToRegister = this.getViewsByContainer(viewContainer); - if (viewsToRegister.length) { - this.addViews(viewContainer, viewsToRegister); - } + return viewDescriptorCollection; } private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 5092ec55d40..05e8d55369e 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -127,12 +127,12 @@ suite('BackupRestorer', () => { for (const editor of editorService.editors) { const resource = editor.getResource(); if (isEqual(resource, untitledFile1)) { - const model = await accessor.textFileService.untitled.resolve({ resource }); + const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); assert.equal(model.textEditorModel.getValue(), 'untitled-1'); model.dispose(); counter++; } else if (isEqual(resource, untitledFile2)) { - const model = await accessor.textFileService.untitled.resolve({ resource }); + const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource }); assert.equal(model.textEditorModel.getValue(), 'untitled-2'); model.dispose(); counter++; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 6f9ae847448..3fd30524c75 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, isResourceTextEdit, TextEdit, ResourceTextEdit, ResourceFileEdit } from 'vs/editor/common/modes'; +import { WorkspaceEdit, TextEdit, WorkspaceTextEdit, WorkspaceFileEdit } from 'vs/editor/common/modes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mergeSort, coalesceInPlace } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; @@ -59,7 +59,7 @@ export class BulkFileOperation extends CheckedObject { type: BulkFileOperationType = 0; textEdits: BulkTextEdit[] = []; - originalEdits = new Map(); + originalEdits = new Map(); newUri?: URI; constructor( @@ -69,10 +69,10 @@ export class BulkFileOperation extends CheckedObject { super(parent._onDidChangeCheckedState); } - addEdit(index: number, type: BulkFileOperationType, edit: ResourceTextEdit | ResourceFileEdit, ) { + addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit, ) { this.type += type; this.originalEdits.set(index, edit); - if (isResourceTextEdit(edit)) { + if (WorkspaceTextEdit.is(edit)) { this.textEdits = this.textEdits.concat(edit.edits.map(edit => new BulkTextEdit(this, edit, this._emitter))); } else if (type === BulkFileOperationType.Rename) { @@ -117,7 +117,7 @@ export class BulkFileOperations { let uri: URI; let type: BulkFileOperationType; - if (isResourceTextEdit(edit)) { + if (WorkspaceTextEdit.is(edit)) { type = BulkFileOperationType.TextEdit; uri = edit.resource; @@ -194,8 +194,8 @@ export class BulkFileOperations { file.originalEdits.forEach((value, idx) => { - if (isResourceTextEdit(value)) { - let newValue: ResourceTextEdit = { ...value, edits: [] }; + if (WorkspaceTextEdit.is(value)) { + let newValue: WorkspaceTextEdit = { ...value, edits: [] }; let allEditsAccepted = true; for (let edit of value.edits) { if (!checkedEdits.has(keyOfEdit(edit))) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 8feeb14711d..eb036defdbe 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -14,6 +14,7 @@ import { escape } from 'vs/base/common/strings'; import { ContentWidgetPositionPreference, IActiveCodeEditor, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { EditorAction, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { FontStyle, LanguageIdentifier, StandardTokenType, TokenMetadata, DocumentSemanticTokensProviderRegistry, SemanticTokensLegend, SemanticTokens } from 'vs/editor/common/modes'; @@ -111,11 +112,16 @@ class InspectEditorTokens extends EditorAction { } } -interface ICompleteLineTokenization { - startState: StackElement | null; - tokens1: IToken[]; - tokens2: Uint32Array; - endState: StackElement; +interface ITextMateTokenInfo { + token: IToken; + metadata: IDecodedMetadata; +} + +interface ISemanticTokenInfo { + type: string; + modifiers: string[]; + range: Range; + metadata: IDecodedMetadata } interface IDecodedMetadata { @@ -221,10 +227,12 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { this._domNode.appendChild(document.createTextNode(nls.localize('inspectTMScopesWidget.loading', "Loading..."))); Promise.all([this._grammar, this._semanticTokens]).then(([grammar, semanticTokens]) => { - if (!grammar) { - throw new Error(`Could not find grammar for language!`); + if (this._isDisposed) { + return; } - this._compute(grammar, semanticTokens, position); + let text = this._compute(grammar, semanticTokens, position); + this._domNode.innerHTML = text; + this._editor.layoutContentWidget(this); }, (err) => { this._notificationService.warn(err); @@ -235,85 +243,62 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } - private _compute(grammar: IGrammar, semanticTokens: SemanticTokensResult | null, position: Position): void { - if (this._isDisposed) { - return; - } - let data = this._getTokensAtLine(grammar, position.lineNumber); + private _compute(grammar: IGrammar | null, semanticTokens: SemanticTokensResult | null, position: Position): string { + const textMateTokenInfo = grammar && this._getTokensAtPosition(grammar, position); + const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position); - let token1Index = 0; - for (let i = data.tokens1.length - 1; i >= 0; i--) { - let t = data.tokens1[i]; - if (position.column - 1 >= t.startIndex) { - token1Index = i; - break; - } + let tokenText; + let metadata: IDecodedMetadata | undefined; + let primary: IDecodedMetadata | undefined; + if (textMateTokenInfo) { + let tokenStartIndex = textMateTokenInfo.token.startIndex; + let tokenEndIndex = textMateTokenInfo.token.endIndex; + tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex); + metadata = textMateTokenInfo.metadata; + primary = semanticTokenInfo?.metadata; + } else if (semanticTokenInfo) { + tokenText = this._model.getValueInRange(semanticTokenInfo.range); + metadata = semanticTokenInfo.metadata; + } else { + return 'No grammar or semantic tokens available.'; } - let token2Index = 0; - for (let i = (data.tokens2.length >>> 1); i >= 0; i--) { - if (position.column - 1 >= data.tokens2[(i << 1)]) { - token2Index = i; - break; - } - } - - let semanticMetadata: IDecodedMetadata | undefined = undefined; - let semanticClasssification; - if (semanticTokens) { - semanticClasssification = this._getSemanticTokenAtPosition(semanticTokens, position); - if (semanticClasssification) { - const metadata = this._themeService.getTheme().getTokenStyleMetadata(semanticClasssification.type, semanticClasssification.modifiers); - if (metadata) { - semanticMetadata = this._decodeMetadata(metadata); - } - } - } - - let result = ''; - - let tokenStartIndex = data.tokens1[token1Index].startIndex; - let tokenEndIndex = data.tokens1[token1Index].endIndex; - let tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex); result += `

${renderTokenText(tokenText)}(${tokenText.length} ${tokenText.length === 1 ? 'char' : 'chars'})

`; - result += ``; - let metadata = this._decodeMetadata(data.tokens2[(token2Index << 1) + 1]); result += ``; result += ``; result += ``; - if (semanticClasssification) { - result += ``; - - const modifiers = semanticClasssification.modifiers.join(' ') || '-'; + if (semanticTokenInfo) { + result += ``; + const modifiers = semanticTokenInfo.modifiers.join(' ') || '-'; result += ``; } result += ``; result += ``; result += ``; - result += this._formatMetadata(metadata, semanticMetadata); + result += this._formatMetadata(metadata, primary); result += ``; - let theme = this._themeService.getColorTheme(); - result += ``; - let matchingRule = findMatchingThemeRule(theme, data.tokens1[token1Index].scopes, false); - if (matchingRule) { - result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } else { - result += `No theme selector.`; - } + if (textMateTokenInfo) { + let theme = this._themeService.getColorTheme(); + result += ``; + let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); + if (matchingRule) { + result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; + } else { + result += `No theme selector.`; + } - result += `
    `; - for (let i = data.tokens1[token1Index].scopes.length - 1; i >= 0; i--) { - result += `
  • ${escape(data.tokens1[token1Index].scopes[i])}
  • `; + result += `
      `; + for (let i = textMateTokenInfo.token.scopes.length - 1; i >= 0; i--) { + result += `
    • ${escape(textMateTokenInfo.token.scopes[i])}
    • `; + } + result += `
    `; } - result += `
`; - - this._domNode.innerHTML = result; - this._editor.layoutContentWidget(this); + return result; } private _formatMetadata(metadata: IDecodedMetadata, master?: IDecodedMetadata) { @@ -381,17 +366,33 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return r; } - private _getTokensAtLine(grammar: IGrammar, lineNumber: number): ICompleteLineTokenization { + private _getTokensAtPosition(grammar: IGrammar, position: Position): ITextMateTokenInfo { + const lineNumber = position.lineNumber; let stateBeforeLine = this._getStateBeforeLine(grammar, lineNumber); let tokenizationResult1 = grammar.tokenizeLine(this._model.getLineContent(lineNumber), stateBeforeLine); let tokenizationResult2 = grammar.tokenizeLine2(this._model.getLineContent(lineNumber), stateBeforeLine); + let token1Index = 0; + for (let i = tokenizationResult1.tokens.length - 1; i >= 0; i--) { + let t = tokenizationResult1.tokens[i]; + if (position.column - 1 >= t.startIndex) { + token1Index = i; + break; + } + } + + let token2Index = 0; + for (let i = (tokenizationResult2.tokens.length >>> 1); i >= 0; i--) { + if (position.column - 1 >= tokenizationResult2.tokens[(i << 1)]) { + token2Index = i; + break; + } + } + return { - startState: stateBeforeLine, - tokens1: tokenizationResult1.tokens, - tokens2: tokenizationResult2.tokens, - endState: tokenizationResult1.ruleStack + token: tokenizationResult1.tokens[token1Index], + metadata: this._decodeMetadata(tokenizationResult2.tokens[(token2Index << 1) + 1]) }; } @@ -422,7 +423,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return null; } - private _getSemanticTokenAtPosition(semanticTokens: SemanticTokensResult, pos: Position): { type: string, modifiers: string[] } | null { + private _getSemanticTokenAtPosition(semanticTokens: SemanticTokensResult, pos: Position): ISemanticTokenInfo | null { const tokenData = semanticTokens.tokens.data; let lastLine = 0; let lastCharacter = 0; @@ -432,10 +433,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const line = lastLine + lineDelta; // 0-based const character = lineDelta === 0 ? lastCharacter + charDelta : charDelta; // 0-based if (posLine === line && character <= posCharacter && posCharacter < character + len) { - return { - type: semanticTokens.legend.tokenTypes[typeIdx], - modifiers: semanticTokens.legend.tokenModifiers.filter((_, k) => modSet & 1 << k) - }; + const type = semanticTokens.legend.tokenTypes[typeIdx]; + const modifiers = semanticTokens.legend.tokenModifiers.filter((_, k) => modSet & 1 << k); + const range = new Range(line + 1, character + 1, line + 1, character + 1 + len); + const metadata = this._decodeMetadata(this._themeService.getTheme().getTokenStyleMetadata(type, modifiers) || 0); + return { type, modifiers, range, metadata }; } lastLine = line; lastCharacter = character; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index cc3b9df79af..33e5c3172f7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -163,15 +163,12 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return await super.resolve(); } - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { + private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave({ - availableFileSystems, - defaultUri - }); + return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); } public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index d65a6f30be2..d0f1548d52b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -239,7 +239,8 @@ export function registerCommands(): void { id: STEP_INTO_ID, weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, - when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), + // Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times + when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn()); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 7aa4b67fd25..a6c75b4d2ae 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -904,6 +904,7 @@ export class DebugSession implements IDebugSession { this.raw.dispose(); } this.raw = undefined; + this.fetchThreadsScheduler = undefined; this.model.clearThreads(this.getId(), true); this._onDidChangeState.fire(); } diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 20d546e3a31..77f2180954a 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -21,7 +21,7 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE items: { additionalProperties: false, type: 'object', - defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [''] } } }], + defaultSnippets: [{ body: { type: '', program: '', runtime: '' } }], properties: { type: { description: nls.localize('vscode.extension.contributes.debuggers.type', "Unique identifier for this debug adapter."), @@ -59,10 +59,6 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE description: nls.localize('vscode.extension.contributes.debuggers.languages', "List of languages for which the debug extension could be considered the \"default debugger\"."), type: 'array' }, - adapterExecutableCommand: { - description: nls.localize('vscode.extension.contributes.debuggers.adapterExecutableCommand', "If specified VS Code will call this command to determine the executable path of the debug adapter and the arguments to pass."), - type: 'string' - }, configurationSnippets: { description: nls.localize('vscode.extension.contributes.debuggers.configurationSnippets', "Snippets for adding new configurations in \'launch.json\'."), type: 'array' diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index ebf20e84685..9a74bddad66 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -411,6 +411,7 @@ class HelpPanelDescriptor implements IViewDescriptor { readonly canToggleVisibility = true; readonly hideByDefault = false; readonly workspace = true; + readonly group = 'help@50'; constructor(viewModel: IViewModel) { this.ctorDescriptor = new SyncDescriptor(HelpPanel, [viewModel]); @@ -542,6 +543,11 @@ Registry.as(Extensions.ViewContainersRegistry).register return -500; } + matches = /^help(@(\d+))?$/.exec(group); + if (matches) { + return -10; + } + return; } }, diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 4a16572e0dd..926d7a85986 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -95,7 +95,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { } if (this.model.detected.size > 0) { groups.push({ - label: nls.localize('remote.tunnelsView.detected', "Detected"), + label: nls.localize('remote.tunnelsView.detected', "Existing Tunnels"), tunnelType: TunnelType.Detected, items: this.detected }); @@ -103,7 +103,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { const candidates = await this.candidates; if (candidates.length > 0) { groups.push({ - label: nls.localize('remote.tunnelsView.candidates', "Candidates"), + label: nls.localize('remote.tunnelsView.candidates', "Not Forwarded"), tunnelType: TunnelType.Candidate, items: candidates }); @@ -369,7 +369,7 @@ class TunnelItem implements ITunnelItem { } else if (this.localAddress) { return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} \u2192 {1}", this.remotePort, this.localAddress); } else if (this.remoteHost !== 'localhost') { - return nls.localize('remote.tunnelsView.forwardedPortLabel4', "{0}:{1} not forwarded", this.remoteHost, this.remotePort); + return nls.localize('remote.tunnelsView.forwardedPortLabel4', "{0}:{1}", this.remoteHost, this.remotePort); } else { return nls.localize('remote.tunnelsView.forwardedPortLabel5', "{0} not forwarded", this.remotePort); } diff --git a/src/vs/workbench/contrib/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts index 13a49276378..2dce71cedfe 100644 --- a/src/vs/workbench/contrib/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -18,7 +18,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { ITextModel, IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceTextEdit } from 'vs/editor/common/modes'; +import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; @@ -101,7 +101,7 @@ export class ReplaceService implements IReplaceService { replace(files: FileMatch[], progress?: IProgress): Promise; replace(match: FileMatchOrMatch, progress?: IProgress, resource?: URI): Promise; async replace(arg: any, progress: IProgress | undefined = undefined, resource: URI | null = null): Promise { - const edits: ResourceTextEdit[] = this.createEdits(arg, resource); + const edits: WorkspaceTextEdit[] = this.createEdits(arg, resource); await this.bulkEditorService.apply({ edits }, { progress }); return Promise.all(edits.map(e => { @@ -173,8 +173,8 @@ export class ReplaceService implements IReplaceService { replaceModel.pushEditOperations([], mergeSort(modelEdits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)), () => []); } - private createEdits(arg: FileMatchOrMatch | FileMatch[], resource: URI | null = null): ResourceTextEdit[] { - const edits: ResourceTextEdit[] = []; + private createEdits(arg: FileMatchOrMatch | FileMatch[], resource: URI | null = null): WorkspaceTextEdit[] { + const edits: WorkspaceTextEdit[] = []; if (arg instanceof Match) { const match = arg; @@ -197,9 +197,9 @@ export class ReplaceService implements IReplaceService { return edits; } - private createEdit(match: Match, text: string, resource: URI | null = null): ResourceTextEdit { + private createEdit(match: Match, text: string, resource: URI | null = null): WorkspaceTextEdit { const fileMatch: FileMatch = match.parent(); - const resourceEdit: ResourceTextEdit = { + const resourceEdit: WorkspaceTextEdit = { resource: resource !== null ? resource : fileMatch.resource, edits: [{ range: match.range(), diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index a60f85060e3..96073a7c9a4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -39,7 +39,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, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction, OpenSearchEditorAction } 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, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; @@ -628,6 +628,36 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ handler: toggleRegexCommand }, ToggleRegexKeybinding)); +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: Constants.ToggleSearchEditorCaseSensitiveCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorCaseSensitiveCommand +}, ToggleCaseSensitiveKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: Constants.ToggleSearchEditorWholeWordCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorWholeWordCommand +}, ToggleWholeWordKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule(objects.assign({ + id: Constants.ToggleSearchEditorRegexCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.InSearchEditor, Constants.SearchInputBoxFocusedKey), + handler: toggleSearchEditorRegexCommand +}, ToggleRegexKeybinding)); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: Constants.ToggleSearchEditorContextLinesCommandId, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(Constants.InSearchEditor), + handler: toggleSearchEditorContextLinesCommand, + primary: KeyMod.Alt | KeyCode.KEY_L, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: Constants.AddCursorsAtSearchResults, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 3b0594206f1..043d44d382a 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -87,14 +87,29 @@ export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => { } }; +export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleCaseSensitive(); + } +}; + export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { - searchView.toggleWholeWords(); } }; +export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleWholeWords(); + } +}; + export const toggleRegexCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); if (searchView) { @@ -102,6 +117,22 @@ export const toggleRegexCommand = (accessor: ServicesAccessor) => { } }; +export const toggleSearchEditorRegexCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleRegex(); + } +}; + +export const toggleSearchEditorContextLinesCommand = (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeControl as SearchEditor).toggleContextLines(); + } +}; + export class FocusNextInputAction extends Action { static readonly ID = 'search.focus.nextInputBox'; diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index aba45bc5dcf..b6cdddb7a6a 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -38,6 +38,7 @@ import { serializeSearchResultForEditor, SearchConfiguration, SearchEditorInput import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InSearchEditor, InputBoxFocusedKey } from 'vs/workbench/contrib/search/common/constants'; +import { IEditorProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; @@ -63,6 +64,8 @@ export class SearchEditor extends BaseEditor { private showingIncludesExcludes: boolean = false; private inSearchEditorContextKey: IContextKey; private inputFocusContextKey: IContextKey; + private searchOperation: LongRunningOperation; + private searchHistoryDelayer: Delayer; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -77,10 +80,13 @@ export class SearchEditor extends BaseEditor { @ICommandService private readonly commandService: ICommandService, @ITextFileService private readonly textFileService: ITextFileService, @IContextKeyService readonly contextKeyService: IContextKeyService, + @IEditorProgressService readonly progressService: IEditorProgressService, ) { super(SearchEditor.ID, telemetryService, themeService, storageService); this.inSearchEditorContextKey = InSearchEditor.bindTo(contextKeyService); this.inputFocusContextKey = InputBoxFocusedKey.bindTo(contextKeyService); + this.searchOperation = this._register(new LongRunningOperation(progressService)); + this.searchHistoryDelayer = new Delayer(2000); } createEditor(parent: HTMLElement) { @@ -168,8 +174,6 @@ export class SearchEditor extends BaseEditor { } }); - - this._register(this.searchResultEditor.onKeyDown(e => e.keyCode === KeyCode.Escape && this.queryEditorWidget.searchInput.focus())); this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); @@ -210,6 +214,25 @@ export class SearchEditor extends BaseEditor { } } + toggleWholeWords() { + this.queryEditorWidget.searchInput.setWholeWords(!this.queryEditorWidget.searchInput.getWholeWords()); + this.runSearch(); + } + + toggleRegex() { + this.queryEditorWidget.searchInput.setRegex(!this.queryEditorWidget.searchInput.getRegex()); + this.runSearch(); + } + + toggleCaseSensitive() { + this.queryEditorWidget.searchInput.setCaseSensitive(!this.queryEditorWidget.searchInput.getCaseSensitive()); + this.runSearch(); + } + + toggleContextLines() { + this.queryEditorWidget.toggleContextLines(); + } + private async runSearch(instant = false) { if (!this.pauseSearching) { this.runSearchDelayer.trigger(() => this.doRunSearch(), instant ? 0 : undefined); @@ -219,6 +242,12 @@ export class SearchEditor extends BaseEditor { private async doRunSearch() { const startInput = this.input; + this.searchHistoryDelayer.trigger(() => { + this.queryEditorWidget.searchInput.onSearchSubmit(); + this.inputPatternExcludes.onSearchSubmit(); + this.inputPatternIncludes.onSearchSubmit(); + }); + const config: SearchConfiguration = { caseSensitive: this.queryEditorWidget.searchInput.getCaseSensitive(), contextLines: this.queryEditorWidget.contextLines(), @@ -268,7 +297,8 @@ export class SearchEditor extends BaseEditor { return; } const searchModel = this.instantiationService.createInstance(SearchModel); - await searchModel.search(query); + this.searchOperation.start(500); + await searchModel.search(query).finally(() => this.searchOperation.stop()); if (this.input !== startInput) { searchModel.dispose(); return; @@ -371,6 +401,7 @@ export class SearchEditor extends BaseEditor { } clearInput() { + super.clearInput(); this.inSearchEditorContextKey.set(false); } } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts index f01dd38d815..4d466200e85 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorCommands.ts @@ -157,7 +157,7 @@ export class SearchEditorInput extends EditorInput { async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { - const path = await this.promptForPath(this.resource, this.suggestFileName()); + const path = await this.promptForPath(this.resource, this.suggestFileName(), options?.availableFileSystems); if (path) { if (await this.textFileService.saveAs(this.resource, path, options)) { this.setDirty(false); @@ -179,13 +179,10 @@ export class SearchEditorInput extends EditorInput { // Brining this over from textFileService because it only suggests for untitled scheme. // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... - private async promptForPath(resource: URI, defaultUri: URI): Promise { + private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave({ - defaultUri, - title: localize('saveAsTitle', "Save As"), - }); + return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); } getTypeId(): string { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 574dfdc13eb..ad51875c1d4 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -371,26 +371,29 @@ export class SearchWidget extends Widget { this.showContextCheckbox = new Checkbox({ isChecked: false, title: nls.localize('showContext', "Show Context"), actionClassName: 'codicon-list-selection' }); - this._register(this.showContextCheckbox.onChange(() => { - dom.toggleClass(parent, 'show-context', this.showContextCheckbox.checked); - this._onDidToggleContext.fire(); - })); + this._register(this.showContextCheckbox.onChange(() => this.onContextLinesChanged())); if (options.showContextToggle) { this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number' }); dom.addClass(this.contextLinesInput.element, 'context-lines-input'); this.contextLinesInput.value = '2'; - this._register(this.contextLinesInput.onDidChange(() => { - if (this.contextLinesInput.value.includes('-')) { - this.contextLinesInput.value = '0'; - } - this._onDidToggleContext.fire(); - })); + this._register(this.contextLinesInput.onDidChange(() => this.onContextLinesChanged())); this._register(attachInputBoxStyler(this.contextLinesInput, this.themeService)); dom.append(searchInputContainer, this.showContextCheckbox.domNode); } } + private onContextLinesChanged() { + dom.toggleClass(this.domNode, 'show-context', this.showContextCheckbox.checked); + this._onDidToggleContext.fire(); + + if (this.contextLinesInput.value.includes('-')) { + this.contextLinesInput.value = '0'; + } + + this._onDidToggleContext.fire(); + } + public setContextLines(lines: number) { if (!this.contextLinesInput) { return; } if (lines === 0) { @@ -504,14 +507,14 @@ export class SearchWidget extends Widget { try { const regex = new RegExp(this.searchInput.getValue(), 'ug'); const matchienessHeuristic = ` -~!@#$%^&*()_+ -\`1234567890-= -qwertyuiop[]\\ -QWERTYUIOP{}| -asdfghjkl;' -ASDFGHJKL:" -zxcvbnm,./ -ZXCVBNM<>? `.match(regex)?.length ?? 0; + ~!@#$%^&*()_+ + \`1234567890-= + qwertyuiop[]\\ + QWERTYUIOP{}| + asdfghjkl;' + ASDFGHJKL:" + zxcvbnm,./ + ZXCVBNM<>? `.match(regex)?.length ?? 0; const delayMultiplier = matchienessHeuristic < 50 ? 1 : @@ -652,6 +655,11 @@ ZXCVBNM<>? `.match(regex)?.length ?? 0; return this.showContextCheckbox.checked ? +this.contextLinesInput.value : 0; } + toggleContextLines() { + this.showContextCheckbox.checked = !this.showContextCheckbox.checked; + this.onContextLinesChanged(); + } + dispose(): void { this.setReplaceAllActionState(false); super.dispose(); diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 54903429e6b..174e6efdc10 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -28,6 +28,10 @@ export const CloseReplaceWidgetActionId = 'closeReplaceInFilesWidget'; export const ToggleCaseSensitiveCommandId = 'toggleSearchCaseSensitive'; export const ToggleWholeWordCommandId = 'toggleSearchWholeWord'; export const ToggleRegexCommandId = 'toggleSearchRegex'; +export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; +export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; +export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; +export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 894bb3d1f7f..4625d60b867 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -48,6 +48,7 @@ const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workben const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`)); const MSA = 'MSA'; +type ConfigureSyncQuickPickItem = { id: string, label: string, description?: string }; export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -253,90 +254,88 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async turnOn(): Promise { - const message = localize('turn on sync', "Turn on Sync"); - let detail: string, primaryButton: string; - if (this.authenticationState.get() === MSAAuthStatus.SignedIn) { - detail = `${localize('turn on sync detail', "This will synchronize your following data across all your devices.")}\n${this.getSyncAreasString()}`; - primaryButton = localize('turn on', "Turn on"); - } else { - detail = `${localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", this.userDataSyncStore!.account)}\n${this.getSyncAreasString()}`; - primaryButton = localize('sign in and turn on sync', "Sign in & Turn on"); - } - const result = await this.dialogService.show( - Severity.Info, message, - [ - primaryButton, - localize('cancel', "Cancel"), - localize('configure', "Configure...") - ], - { - detail, - cancelId: 1 - }); - switch (result.choice) { - case 1: return; - case 2: await this.configureSyncOptions(); return this.turnOn(); - } + return new Promise((c, e) => { + const disposables: DisposableStore = new DisposableStore(); + const quickPick = this.quickInputService.createQuickPick(); + disposables.add(quickPick); + quickPick.title = localize('turn on sync', "Turn on Sync"); + quickPick.ok = false; + quickPick.customButton = true; + if (this.authenticationState.get() === MSAAuthStatus.SignedIn) { + quickPick.description = localize('turn on sync detail', "Turn on to synchronize your following data across all your devices."); + quickPick.customLabel = localize('turn on', "Turn on"); + } else { + quickPick.description = localize('sign in and turn on sync detail', "Please sign in with your {0} account to synchronize your following data across all your devices.", this.userDataSyncStore!.account); + quickPick.customLabel = localize('sign in and turn on sync', "Sign in & Turn on"); + } + quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + const items = this.getConfigureSyncQuickPickItems(); + quickPick.items = items; + quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + disposables.add(Event.any(quickPick.onDidAccept, quickPick.onDidCustom)(async () => { + if (quickPick.selectedItems.length) { + await this.updateConfiguration(items, quickPick.selectedItems); + this.doTurnOn().then(c, e); + quickPick.hide(); + } + })); + disposables.add(quickPick.onDidHide(() => disposables.dispose())); + quickPick.show(); + }); + } + + private async doTurnOn(): Promise { if (this.authenticationState.get() === MSAAuthStatus.SignedOut) { await this.signIn(); } - await this.handleFirstTimeSync(); await this.enableSync(); } - private getSyncAreasString(): string { - const { enableSettings, enableKeybindings, enableExtensions, enableUIState } = this.configurationService.getValue<{ enableSettings: boolean, enableKeybindings: boolean, enableExtensions: boolean, enableUIState: boolean }>('sync'); - let result = ''; - if (enableSettings) { - result += '\n - ' + localize('settings', "Settings"); + private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { + return [{ + id: 'sync.enableSettings', + label: localize('settings', "Settings") + }, { + id: 'sync.enableKeybindings', + label: localize('keybindings', "Keybindings") + }, { + id: 'sync.enableExtensions', + label: localize('extensions', "Extensions") + }, { + id: 'sync.enableUIState', + label: localize('ui state label', "UI State"), + description: localize('ui state description', "Display Language (Only)") + }]; + } + + private async updateConfiguration(items: ConfigureSyncQuickPickItem[], selectedItems: ReadonlyArray): Promise { + for (const item of items) { + const wasEnabled = this.configurationService.getValue(item.id); + const isEnabled = !!selectedItems.filter(selected => selected.id === item.id)[0]; + if (wasEnabled !== isEnabled) { + await this.configurationService.updateValue(item.id!, isEnabled); + } } - if (enableKeybindings) { - result += '\n - ' + localize('keybindings', "Keybindings"); - } - if (enableExtensions) { - result += '\n - ' + localize('extensions', "Extensions"); - } - if (enableUIState) { - result += '\n - ' + localize('ui state', "UI State (Display Language Only)"); - } - return result; } private async configureSyncOptions(): Promise { return new Promise((c, e) => { const disposables: DisposableStore = new DisposableStore(); - const quickPick = this.quickInputService.createQuickPick(); + const quickPick = this.quickInputService.createQuickPick(); disposables.add(quickPick); - quickPick.title = localize('configure sync title', "Sync: Configure What to Sync"); + quickPick.title = localize('turn on sync', "Turn on Sync"); quickPick.placeholder = localize('configure sync placeholder', "Choose what to sync"); quickPick.canSelectMany = true; quickPick.ignoreFocusOut = true; - const items = [{ - id: 'sync.enableSettings', - label: localize('settings', "Settings") - }, { - id: 'sync.enableKeybindings', - label: localize('keybindings', "Keybindings") - }, { - id: 'sync.enableExtensions', - label: localize('extensions', "Extensions") - }, { - id: 'sync.enableUIState', - label: localize('ui state label', "UI State"), - description: localize('ui state description', "Display Language (Only)") - }]; + const items = this.getConfigureSyncQuickPickItems(); quickPick.items = items; quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); disposables.add(quickPick.onDidAccept(async () => { if (quickPick.selectedItems.length) { - for (const item of items) { - const wasEnabled = this.configurationService.getValue(item.id); - const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; - if (wasEnabled !== isEnabled) { - await this.configurationService.updateValue(item.id!, isEnabled); - } - } + await this.updateConfiguration(items, quickPick.selectedItems); quickPick.hide(); } })); diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index 68d9617bc8f..ab2af19a26a 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -66,7 +66,7 @@ export default () => `
-
+
diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 3a0d834deb7..0a197e925b8 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -11,7 +11,7 @@ import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHa import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { isResourceFileEdit, isResourceTextEdit, ResourceFileEdit, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; +import { WorkspaceFileEdit, WorkspaceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; @@ -47,7 +47,7 @@ class ModelEditTask implements IDisposable { this._modelReference.dispose(); } - addEdit(resourceEdit: ResourceTextEdit): void { + addEdit(resourceEdit: WorkspaceTextEdit): void { this._expectedModelVersionId = resourceEdit.modelVersionId; for (const edit of resourceEdit.edits) { if (typeof edit.eol === 'number') { @@ -124,13 +124,13 @@ class EditorEditTask extends ModelEditTask { class BulkEditModel implements IDisposable { - private _edits = new Map(); + private _edits = new Map(); private _tasks: ModelEditTask[] | undefined; constructor( private readonly _editor: ICodeEditor | undefined, private readonly _progress: IProgress, - edits: ResourceTextEdit[], + edits: WorkspaceTextEdit[], @IEditorWorkerService private readonly _editorWorker: IEditorWorkerService, @ITextModelService private readonly _textModelResolverService: ITextModelService, ) { @@ -143,7 +143,7 @@ class BulkEditModel implements IDisposable { } } - private _addEdit(edit: ResourceTextEdit): void { + private _addEdit(edit: WorkspaceTextEdit): void { let array = this._edits.get(edit.resource.toString()); if (!array) { array = []; @@ -217,7 +217,7 @@ class BulkEditModel implements IDisposable { } } -type Edit = ResourceFileEdit | ResourceTextEdit; +type Edit = WorkspaceFileEdit | WorkspaceTextEdit; class BulkEdit { @@ -243,7 +243,7 @@ class BulkEdit { } ariaMessage(): string { - const editCount = this._edits.reduce((prev, cur) => isResourceFileEdit(cur) ? prev : prev + cur.edits.length, 0); + const editCount = this._edits.reduce((prev, cur) => WorkspaceFileEdit.is(cur) ? prev : prev + cur.edits.length, 0); const resourceCount = this._edits.length; if (editCount === 0) { return localize('summary.0', "Made no edits"); @@ -263,15 +263,15 @@ class BulkEdit { let group: Edit[] | undefined; for (const edit of this._edits) { if (!group - || (isResourceFileEdit(group[0]) && !isResourceFileEdit(edit)) - || (isResourceTextEdit(group[0]) && !isResourceTextEdit(edit)) + || (WorkspaceFileEdit.is(group[0]) && !WorkspaceFileEdit.is(edit)) + || (WorkspaceTextEdit.is(group[0]) && !WorkspaceTextEdit.is(edit)) ) { group = []; groups.push(group); } group.push(edit); - if (isResourceFileEdit(edit)) { + if (WorkspaceFileEdit.is(edit)) { total += 1; } else if (!seen.has(edit.resource.toString())) { seen.add(edit.resource.toString()); @@ -287,15 +287,15 @@ class BulkEdit { // do it. for (const group of groups) { - if (isResourceFileEdit(group[0])) { - await this._performFileEdits(group, progress); + if (WorkspaceFileEdit.is(group[0])) { + await this._performFileEdits(group, progress); } else { - await this._performTextEdits(group, progress); + await this._performTextEdits(group, progress); } } } - private async _performFileEdits(edits: ResourceFileEdit[], progress: IProgress) { + private async _performFileEdits(edits: WorkspaceFileEdit[], progress: IProgress) { this._logService.debug('_performFileEdits', JSON.stringify(edits)); for (const edit of edits) { progress.report(undefined); @@ -330,7 +330,7 @@ class BulkEdit { } } - private async _performTextEdits(edits: ResourceTextEdit[], progress: IProgress): Promise { + private async _performTextEdits(edits: WorkspaceTextEdit[], progress: IProgress): Promise { this._logService.debug('_performTextEdits', JSON.stringify(edits)); const recording = Recording.start(this._fileService); @@ -402,7 +402,7 @@ export class BulkEditService implements IBulkEditService { // First check if loaded models were not changed in the meantime for (const edit of edits) { - if (!isResourceFileEdit(edit) && typeof edit.modelVersionId === 'number') { + if (!WorkspaceFileEdit.is(edit) && typeof edit.modelVersionId === 'number') { let model = this._modelService.getModel(edit.resource); if (model && model.getVersionId() !== edit.modelVersionId) { // model changed in the meantime diff --git a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts index 6c9c2dc6c0a..7459a5ab500 100644 --- a/src/vs/workbench/services/bulkEdit/browser/conflicts.ts +++ b/src/vs/workbench/services/bulkEdit/browser/conflicts.ts @@ -5,7 +5,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; -import { WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; +import { WorkspaceEdit, WorkspaceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceMap } from 'vs/base/common/map'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -49,7 +49,7 @@ export class ConflictDetector { const _workspaceEditResources = new ResourceMap(); for (let edit of workspaceEdit.edits) { - if (isResourceTextEdit(edit)) { + if (WorkspaceTextEdit.is(edit)) { _workspaceEditResources.set(edit.resource, true); diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index 60fe745cfc8..aa6dd31e2de 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -21,6 +21,9 @@ 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'; +import { coalesce } from 'vs/base/common/arrays'; +import { trim } from 'vs/base/common/strings'; +import { IModeService } from 'vs/editor/common/services/modeService'; export abstract class AbstractFileDialogService implements IFileDialogService { @@ -35,7 +38,8 @@ export abstract class AbstractFileDialogService implements IFileDialogService { @IConfigurationService protected readonly configurationService: IConfigurationService, @IFileService protected readonly fileService: IFileService, @IOpenerService protected readonly openerService: IOpenerService, - @IDialogService private readonly dialogService: IDialogService + @IDialogService private readonly dialogService: IDialogService, + @IModeService private readonly modeService: IModeService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -222,7 +226,56 @@ export abstract class AbstractFileDialogService implements IFileDialogService { abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise; abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise; abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise; - abstract pickFileToSave(options: ISaveDialogOptions): Promise; abstract showSaveDialog(options: ISaveDialogOptions): Promise; abstract showOpenDialog(options: IOpenDialogOptions): Promise; + + abstract pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise; + + protected getPickFileToSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As"), + availableFileSystems, + }; + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string | undefined = defaultUri ? resources.extname(defaultUri) : undefined; + let matchingFilter: IFilter | undefined; + const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + })); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.unshift(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + // Allow to save file without extension + filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); + + options.filters = filters; + + return options; + } } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index a82239f1701..a25f8e00158 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -51,9 +51,9 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return this.pickWorkspaceAndOpenSimplified(schema, options); } - async pickFileToSave(options: ISaveDialogOptions): Promise { - const schema = this.getFileSystemSchema(options); - return this.pickFileToSaveSimplified(schema, options); + async pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { + const schema = this.getFileSystemSchema({ defaultUri, availableFileSystems }); + return this.pickFileToSaveSimplified(schema, this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems)); } async showSaveDialog(options: ISaveDialogOptions): Promise { diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index af8287287a9..a91c1ed01c8 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -19,6 +19,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; import { Schemas } from 'vs/base/common/network'; +import { IModeService } from 'vs/editor/common/services/modeService'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -34,9 +35,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IFileService fileService: IFileService, @IOpenerService openerService: IOpenerService, @IElectronService private readonly electronService: IElectronService, - @IDialogService dialogService: IDialogService + @IDialogService dialogService: IDialogService, + @IModeService modeService: IModeService ) { - super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService); + super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService, modeService); } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { @@ -107,8 +109,9 @@ export class FileDialogService extends AbstractFileDialogService implements IFil return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); } - async pickFileToSave(options: ISaveDialogOptions): Promise { - const schema = this.getFileSystemSchema(options); + async pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { + const schema = this.getFileSystemSchema({ defaultUri, availableFileSystems }); + const options = this.getPickFileToSaveDialogOptions(defaultUri, availableFileSystems); if (this.shouldUseSimplified(schema).useSimplified) { return this.pickFileToSaveSimplified(schema, options); } else { diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index b52fcc37a32..7dcc2e5a4ce 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -610,7 +610,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untitled file support const untitledInput = input as IUntitledTextResourceInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { - return this.untitledTextEditorService.create(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); + return this.untitledTextEditorService.create({ + untitledResource: untitledInput.resource?.scheme === Schemas.untitled ? untitledInput.resource : undefined, + associatedResource: untitledInput.resource?.scheme !== Schemas.untitled ? untitledInput.resource : undefined, + mode: untitledInput.mode, + initialValue: untitledInput.contents, + encoding: untitledInput.encoding + }); } // Resource Editor Support diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index a782f0cd7ac..6248309e3fb 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -322,8 +322,15 @@ export abstract class AbstractTextMateService extends Disposable implements ITex } public async createGrammar(modeId: string): Promise { + const languageId = this._modeService.getLanguageIdentifier(modeId); + if (!languageId) { + return null; + } const grammarFactory = await this._getOrCreateGrammarFactory(); - const { grammar } = await grammarFactory.createGrammar(this._modeService.getLanguageIdentifier(modeId)!.id); + if (!grammarFactory.has(languageId.id)) { + return null; + } + const { grammar } = await grammarFactory.createGrammar(languageId.id); return grammar; } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index df15ba7f6e5..df921c340d1 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; -import * as platform from 'vs/base/common/platform'; import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -22,12 +21,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 { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { isEqualOrParent, isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; +import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { coalesce } from 'vs/base/common/arrays'; -import { trim } from 'vs/base/common/strings'; import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -57,9 +53,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex readonly files = this._register(this.instantiationService.createInstance(TextFileEditorModelManager)); private _untitled: IUntitledTextEditorModelManager; - get untitled(): IUntitledTextEditorModelManager { - return this._untitled; - } + get untitled(): IUntitledTextEditorModelManager { return this._untitled; } abstract get encoding(): IResourceEncodings; @@ -68,7 +62,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService protected readonly lifecycleService: ILifecycleService, @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IHistoryService private readonly historyService: IHistoryService, @@ -184,7 +177,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); const dirtyFiles = this.getDirtyFileModels().map(dirtyFileModel => dirtyFileModel.resource).filter(dirty => isEqualOrParent(dirty, resource)); - await this.doRevertAllFiles(dirtyFiles, { soft: true }); + await this.doRevertFiles(dirtyFiles, { soft: true }); await this.fileService.del(resource, options); @@ -250,7 +243,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // in order to move and copy, we need to soft revert all dirty models, // both from the source as well as the target if any const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.doRevertAllFiles(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); + await this.doRevertFiles(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); // now we can rename the source to target via file operation let stat: IFileStatWithMetadata; @@ -311,7 +304,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Otherwise ask user else { - targetUri = await this.promptForPath(resource, this.suggestFileName(resource)); + targetUri = await this.promptForPath(resource, this.suggestFilePath(resource)); } // Save as if target provided @@ -338,81 +331,26 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return false; } - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); + return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); } - private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: readonly string[]): ISaveDialogOptions { - const options: ISaveDialogOptions = { - defaultUri, - title: nls.localize('saveAsTitle', "Save As"), - availableFileSystems, - }; - - // Filters are only enabled on Windows where they work properly - if (!platform.isWindows) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; - let matchingFilter: IFilter | undefined; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { - const extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - })); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.unshift(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - // Allow to save file without extension - filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); - - options.filters = filters; - - return options; - } - - private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { - if (Array.isArray(arg1)) { + private getFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + if (Array.isArray(resources)) { const models: ITextFileEditorModel[] = []; - arg1.forEach(resource => { - models.push(...this.getFileModels(resource)); - }); + resources.forEach(resource => models.push(...this.getFileModels(resource))); return models; } - return this.files.getAll(arg1); + return this.files.getAll(resources); } - private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + private getDirtyFileModels(resources?: URI[]): ITextFileEditorModel[] { return this.getFileModels(resources).filter(model => model.isDirty()); } @@ -422,7 +360,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (!target) { let dialogPath = source; if (source.scheme === Schemas.untitled) { - dialogPath = this.suggestFileName(source); + dialogPath = this.suggestFilePath(source); } target = await this.promptForPath(source, dialogPath, options ? options.availableFileSystems : undefined); @@ -513,17 +451,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex await this.create(target, ''); } - // Carry over the mode if this is an untitled file and the mode was picked by the user - let mode: string | undefined; - if (sourceModel instanceof UntitledTextEditorModel) { - mode = sourceModel.getMode(); - if (mode === PLAINTEXT_MODE_ID) { - mode = undefined; // never enforce plain text mode when moving as it is unspecific - } - } - try { - targetModel = await this.files.resolve(target, { encoding: sourceModelEncoding, mode }); + targetModel = await this.files.resolve(target, { encoding: sourceModelEncoding }); } catch (error) { // if the target already exists and was not created by us, it is possible // that we cannot load the target as text model if it is binary or too @@ -574,10 +503,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // take over model value, encoding and mode (only if more specific) from source model - targetModel.updatePreferredEncoding(sourceModelEncoding); if (sourceTextModel && targetTextModel) { + + // encoding + targetModel.updatePreferredEncoding(sourceModelEncoding); + + // content this.modelService.updateModel(targetTextModel, createTextBufferFactoryFromSnapshot(sourceTextModel.createSnapshot())); + // mode const sourceMode = sourceTextModel.getLanguageIdentifier(); const targetMode = targetTextModel.getLanguageIdentifier(); if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { @@ -602,7 +536,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return (await this.dialogService.confirm(confirm)).confirmed; } - private suggestFileName(untitledResource: URI): URI { + private suggestFilePath(untitledResource: URI): URI { const untitledFileName = this.untitled.get(untitledResource)?.suggestFileName() ?? basename(untitledResource); const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; @@ -638,10 +572,10 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } // File - return !(await this.doRevertAllFiles([resource], options)).results.some(result => result.error); + return !(await this.doRevertFiles([resource], options)).results.some(result => result.error); } - private async doRevertAllFiles(resources: URI[], options?: IRevertOptions): Promise { + private async doRevertFiles(resources: URI[], options?: IRevertOptions): Promise { const fileModels = options?.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); const mapResourceToResult = new ResourceMap(); diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index b9eca7404e9..0c1c8038bfb 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -30,7 +30,6 @@ import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadabl import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -47,7 +46,6 @@ export class NativeTextFileService extends AbstractTextFileService { @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IHistoryService historyService: IHistoryService, @@ -59,7 +57,7 @@ export class NativeTextFileService extends AbstractTextFileService { @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modeService, modelService, environmentService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 5e2952dc265..57b6da6ea10 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - const model = await this.untitledTextEditorService.resolve({ resource }); + const model = await this.untitledTextEditorService.resolve({ untitledResource: resource }); return new ImmortalReference(model); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index b61c33351d5..e9d5cc0dc23 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; -import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files'; +import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; @@ -14,16 +14,45 @@ import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { UntitledTextEditorModel, IUntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; -import type { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); -export interface IUntitledCreationOptions { - resource?: URI; - mode?: string; +export interface IUntitledTextEditorOptions { + + /** + * An optional resource to identify the untitled resource to create or return + * if already existing. + * + * Note: the resource will not be used unless the scheme is `untitled`. + */ + untitledResource?: URI; + + /** + * Optional resource components to associate with the untitled file. When saving + * the untitled file, the associated components will be used and the user + * is not being asked to provide a file path. + * + * Note: currently it is not possible to specify the `scheme` to use. The + * untitled file will saved to the default local or remote resource. + */ + associatedResource?: { authority: string; path: string; query: string; fragment: string; } + + /** + * Initial value of the untitled file. An untitled file with initial + * value is dirty right from the beginning. + */ initialValue?: string; + + /** + * Preferred language mode to use when saving the untitled file. + */ + mode?: string; + + /** + * Preferred encoding to use when saving the untitled file. + */ encoding?: string; - useResourcePath?: boolean; } export interface IUntitledTextEditorModelManager { @@ -54,20 +83,18 @@ export interface IUntitledTextEditorModelManager { get(resource: URI): UntitledTextEditorInput | undefined; /** - * Creates a new untitled input with the optional resource URI to - * be used as associated file path when saving. + * Creates a new untitled input with the provided options. If the `untitledResource` + * property is provided and the untitled input exists, it will return that existing + * instance instead of creating a new one. */ - create(options?: IUntitledCreationOptions): UntitledTextEditorInput; - create(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; + create(options?: IUntitledTextEditorOptions): UntitledTextEditorInput; /** - * Creates a new untitled model with the optional resource URI or returns an existing one - * if the provided resource exists already as untitled model. - * - * It is valid to pass in a file resource. In that case the path will be used as identifier. - * The use case is to be able to create a new file with a specific path with VSCode. + * Resolves an untitled editor model from the provided options. If the `untitledResource` + * property is provided and the untitled input exists, it will return that existing + * instance instead of creating a new one. */ - resolve(options?: IUntitledCreationOptions): Promise; + resolve(options?: IUntitledTextEditorOptions): Promise; /** * A check to find out if a untitled resource has a file path associated or not. @@ -98,8 +125,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); } @@ -112,72 +138,89 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe return this.mapResourceToInput.get(resource); } - create(options?: IUntitledCreationOptions): UntitledTextEditorInput; - create(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; - create(resourceOrOptions?: URI | IUntitledCreationOptions, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { - if (resourceOrOptions && !URI.isUri(resourceOrOptions)) { - return this.doCreateOrGet(resourceOrOptions.resource, resourceOrOptions.mode, resourceOrOptions.initialValue, resourceOrOptions.encoding, resourceOrOptions.useResourcePath); - } - - return this.doCreateOrGet(resourceOrOptions, mode, initialValue, encoding, hasAssociatedFilePath); + resolve(options?: IUntitledTextEditorOptions): Promise { + return this.doCreateOrGet(options).resolve(); } - private doCreateOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { - if (resource) { + create(options?: IUntitledTextEditorOptions): UntitledTextEditorInput { + return this.doCreateOrGet(options); + } - // Massage resource if it comes with known file based resource - if (this.fileService.canHandleResource(resource)) { - hasAssociatedFilePath = true; - resource = resource.with({ scheme: Schemas.untitled }); // ensure we have the right scheme - } - - if (hasAssociatedFilePath) { - this.mapResourceToAssociatedFilePath.set(resource, true); // remember for future lookups - } - } + private doCreateOrGet(options: IUntitledTextEditorOptions = Object.create(null)): UntitledTextEditorInput { + const massagedOptions = this.massageOptions(options); // Return existing instance if asked for it - if (resource && this.mapResourceToInput.has(resource)) { - return this.mapResourceToInput.get(resource)!; + if (massagedOptions.untitledResource && this.mapResourceToInput.has(massagedOptions.untitledResource)) { + return this.mapResourceToInput.get(massagedOptions.untitledResource)!; } - // Create new otherwise - return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding); + // Create new instance otherwise + return this.doCreate(massagedOptions); } - private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput { - let untitledResource: URI; - if (resource) { - untitledResource = resource; - } else { + private massageOptions(options: IUntitledTextEditorOptions): IUntitledTextEditorOptions { + const massagedOptions: IUntitledTextEditorOptions = Object.create(null); - // Create new taking a resource URI that is not already taken - let counter = this.mapResourceToInput.size + 1; + // Figure out associated and untitled resource + if (options.associatedResource) { + massagedOptions.untitledResource = URI.from({ + scheme: Schemas.untitled, + authority: options.associatedResource.authority, + fragment: options.associatedResource.fragment, + path: options.associatedResource.path, + query: options.associatedResource.query + }); + massagedOptions.associatedResource = options.associatedResource; + } else { + if (options.untitledResource?.scheme === Schemas.untitled) { + massagedOptions.untitledResource = options.untitledResource; + } + } + + // Language mode + if (options.mode) { + massagedOptions.mode = options.mode; + } else if (!massagedOptions.associatedResource) { + const configuration = this.configurationService.getValue(); + if (configuration.files?.defaultLanguage) { + massagedOptions.mode = configuration.files.defaultLanguage; + } + } + + // Take over encoding and initial value + massagedOptions.encoding = options.encoding; + massagedOptions.initialValue = options.initialValue; + + return massagedOptions; + } + + private doCreate(options: IUntitledTextEditorOptions): UntitledTextEditorInput { + + // Create a new untitled resource if none is provided + let untitledResource = options.untitledResource; + if (!untitledResource) { + let counter = 1; do { untitledResource = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}` }); counter++; } while (this.mapResourceToInput.has(untitledResource)); } - // Look up default language from settings if any - if (!mode && !hasAssociatedFilePath) { - const configuration = this.configurationService.getValue(); - if (configuration.files?.defaultLanguage) { - mode = configuration.files.defaultLanguage; - } - } + // Create new input with provided options + const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!options.associatedResource, options.mode, options.initialValue, options.encoding); - const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding); - - const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource)); - const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(untitledResource)); - const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(untitledResource)); + const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(input.getResource())); + const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(input.getResource())); + const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(input.getResource())); // Remove from cache on dispose - const onceDispose = Event.once(input.onDispose); - onceDispose(() => { + Event.once(input.onDispose)(() => { + + // Registry this.mapResourceToInput.delete(input.getResource()); this.mapResourceToAssociatedFilePath.delete(input.getResource()); + + // Listeners dirtyListener.dispose(); encodingListener.dispose(); disposeListener.dispose(); @@ -185,14 +228,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe // Add to cache this.mapResourceToInput.set(untitledResource, input); + if (options.associatedResource) { + this.mapResourceToAssociatedFilePath.set(untitledResource, true); + } return input; } - resolve(options?: IUntitledCreationOptions): Promise { - return this.doCreateOrGet(options?.resource, options?.mode, options?.initialValue, options?.encoding, options?.useResourcePath).resolve(); - } - hasAssociatedFilePath(resource: URI): boolean { return this.mapResourceToAssociatedFilePath.has(resource); } diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index fbe9ef42342..8e23aff7415 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -46,7 +46,7 @@ suite('Workbench untitled text editors', () => { const workingCopyService = accessor.workingCopyService; const input1 = service.create(); - assert.equal(input1, service.create(input1.getResource())); + assert.equal(input1, service.create({ untitledResource: input1.getResource() })); assert.equal(service.get(input1.getResource()), input1); assert.ok(service.exists(input1.getResource())); @@ -66,7 +66,7 @@ suite('Workbench untitled text editors', () => { // dirty const model = await input2.resolve(); - assert.equal(await service.resolve({ resource: input2.getResource() }), model); + assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model); assert.ok(!input2.isDirty()); @@ -109,7 +109,7 @@ suite('Workbench untitled text editors', () => { test('Untitled with associated resource is dirty', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const untitled = service.create(file); + const untitled = service.create({ associatedResource: file }); assert.ok(service.hasAssociatedFilePath(untitled.getResource())); assert.equal(untitled.isDirty(), true); @@ -150,12 +150,12 @@ suite('Workbench untitled text editors', () => { const input = service.create(); - const model3 = await service.create({ resource: input.getResource() }).resolve(); + const model3 = await service.create({ untitledResource: input.getResource() }).resolve(); assert.equal(model3.resource.toString(), input.getResource().toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); - const model4 = await service.create({ resource: file }).resolve(); + const model4 = await service.create({ associatedResource: file }).resolve(); assert.ok(service.hasAssociatedFilePath(model4.resource)); assert.ok(model4.isDirty()); @@ -177,7 +177,7 @@ suite('Workbench untitled text editors', () => { test('Untitled with associated path remains dirty when content gets empty', async () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); - const input = service.create(file); + const input = service.create({ associatedResource: file }); // dirty const model = await input.resolve(); @@ -193,7 +193,7 @@ suite('Workbench untitled text editors', () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; - const untitled = service.create(undefined, undefined, 'Hello World'); + const untitled = service.create({ initialValue: 'Hello World' }); assert.equal(untitled.isDirty(), true); let onDidChangeDirty: IWorkingCopy | undefined = undefined; @@ -251,7 +251,7 @@ suite('Workbench untitled text editors', () => { config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); const service = accessor.untitledTextEditorService; - const input = service.create(null!, mode); + const input = service.create({ mode }); assert.equal(input.getMode(), mode); @@ -268,7 +268,7 @@ suite('Workbench untitled text editors', () => { }); const service = accessor.untitledTextEditorService; - const input = service.create(null!, mode); + const input = service.create({ mode }); assert.equal(input.getMode(), mode); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 759da107345..d0218eda08d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -14,7 +14,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import type * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { NullLogService } from 'vs/platform/log/common/log'; -import { isResourceTextEdit, ResourceTextEdit } from 'vs/editor/common/modes'; +import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { timeout } from 'vs/base/common/async'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -279,8 +279,8 @@ suite('ExtHostDocumentSaveParticipant', () => { sub.dispose(); assert.equal(dto.edits.length, 1); - assert.ok(isResourceTextEdit(dto.edits[0])); - assert.equal((dto.edits[0]).edits.length, 2); + assert.ok(WorkspaceTextEdit.is(dto.edits[0])); + assert.equal((dto.edits[0]).edits.length, 2); }); }); @@ -326,7 +326,7 @@ suite('ExtHostDocumentSaveParticipant', () => { $tryApplyWorkspaceEdit(dto: IWorkspaceEditDto) { for (const edit of dto.edits) { - if (!isResourceTextEdit(edit)) { + if (!WorkspaceTextEdit.is(edit)) { continue; } const { resource, edits } = edit; diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 0208284f9cc..6b928d05c0c 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -759,8 +759,8 @@ suite('ExtHostLanguageFeatures', function () { const value = await rename(model, new EditorPosition(1, 1), 'newName'); // least relevant rename provider assert.equal(value.edits.length, 2); - assert.equal((value.edits[0]).edits.length, 1); - assert.equal((value.edits[1]).edits.length, 1); + assert.equal((value.edits[0]).edits.length, 1); + assert.equal((value.edits[1]).edits.length, 1); }); // --- parameter hints diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts index 771900aaeca..eae86a47ac7 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts @@ -10,7 +10,7 @@ import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; -import { ResourceTextEdit } from 'vs/editor/common/modes'; +import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostTextEditors.applyWorkspaceEdit', () => { @@ -48,7 +48,7 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { edit.replace(resource, new extHostTypes.Range(0, 0, 0, 0), 'hello'); await editors.applyWorkspaceEdit(edit); assert.equal(workspaceResourceEdits.edits.length, 1); - assert.equal((workspaceResourceEdits.edits[0]).modelVersionId, 1337); + assert.equal((workspaceResourceEdits.edits[0]).modelVersionId, 1337); }); test('does not use version id if document is not available', async () => { @@ -56,7 +56,7 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { edit.replace(URI.parse('foo:bar2'), new extHostTypes.Range(0, 0, 0, 0), 'hello'); await editors.applyWorkspaceEdit(edit); assert.equal(workspaceResourceEdits.edits.length, 1); - assert.ok(typeof (workspaceResourceEdits.edits[0]).modelVersionId === 'undefined'); + assert.ok(typeof (workspaceResourceEdits.edits[0]).modelVersionId === 'undefined'); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 9938166c769..6a20c5082ba 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -20,7 +20,7 @@ import { Position } from 'vs/editor/common/core/position'; import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; -import { ResourceTextEdit } from 'vs/editor/common/modes'; +import { WorkspaceTextEdit } from 'vs/editor/common/modes'; import { BulkEditService } from 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import { NullLogService } from 'vs/platform/log/common/log'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -141,7 +141,7 @@ suite('MainThreadEditors', () => { let model = modelService.createModel('something', null, resource); - let workspaceResourceEdit: ResourceTextEdit = { + let workspaceResourceEdit: WorkspaceTextEdit = { resource: resource, modelVersionId: model.getVersionId(), edits: [{ @@ -162,7 +162,7 @@ suite('MainThreadEditors', () => { let model = modelService.createModel('something', null, resource); - let workspaceResourceEdit1: ResourceTextEdit = { + let workspaceResourceEdit1: WorkspaceTextEdit = { resource: resource, modelVersionId: model.getVersionId(), edits: [{ @@ -170,7 +170,7 @@ suite('MainThreadEditors', () => { range: new Range(1, 1, 1, 1) }] }; - let workspaceResourceEdit2: ResourceTextEdit = { + let workspaceResourceEdit2: WorkspaceTextEdit = { resource: resource, modelVersionId: model.getVersionId(), edits: [{ diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index c048560e2d5..caceda8fef7 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -198,7 +198,6 @@ export class TestTextFileService extends NativeTextFileService { @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, - @IModeService modeService: IModeService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IHistoryService historyService: IHistoryService, @@ -215,7 +214,6 @@ export class TestTextFileService extends NativeTextFileService { untitledTextEditorService, lifecycleService, instantiationService, - modeService, modelService, environmentService, historyService, @@ -413,7 +411,7 @@ export class TestFileDialogService implements IFileDialogService { pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - pickFileToSave(_options: ISaveDialogOptions): Promise { + pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { return Promise.resolve(undefined); } showSaveDialog(_options: ISaveDialogOptions): Promise {