diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 76a988cb508..b230a1a4897 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -26,6 +26,7 @@ "notebookMime", "portsAttributes", "quickPickSortByLabel", + "readonlyMessage", "resolvers", "saveEditor", "scmActionButton", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/readonlyFileSystem.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/readonlyFileSystem.test.ts new file mode 100644 index 00000000000..745eba859fb --- /dev/null +++ b/extensions/vscode-api-tests/src/singlefolder-tests/readonlyFileSystem.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { TestFS } from '../memfs'; +import { assertNoRpc, closeAllEditors } from '../utils'; + +suite('vscode API - file system', () => { + + teardown(async function () { + assertNoRpc(); + await closeAllEditors(); + }); + + test('readonly file system - boolean', async function () { + const fs = new TestFS('this-fs', false); + const reg = vscode.workspace.registerFileSystemProvider(fs.scheme, fs, { isReadonly: true }); + let error: any | undefined; + try { + await vscode.workspace.fs.writeFile(vscode.Uri.parse('this-fs:/foo.txt'), Buffer.from('Hello World')); + } catch (e) { + error = e; + } + assert.strictEqual(vscode.workspace.fs.isWritableFileSystem('this-fs'), false); + assert.strictEqual(error instanceof vscode.FileSystemError, true); + const fileError: vscode.FileSystemError = error; + assert.strictEqual(fileError.code, 'NoPermissions'); + reg.dispose(); + }); + + test('readonly file system - markdown', async function () { + const fs = new TestFS('this-fs', false); + const reg = vscode.workspace.registerFileSystemProvider(fs.scheme, fs, { isReadonly: new vscode.MarkdownString('This file is readonly.') }); + let error: any | undefined; + try { + await vscode.workspace.fs.writeFile(vscode.Uri.parse('this-fs:/foo.txt'), Buffer.from('Hello World')); + } catch (e) { + error = e; + } + assert.strictEqual(vscode.workspace.fs.isWritableFileSystem('this-fs'), false); + assert.strictEqual(error instanceof vscode.FileSystemError, true); + const fileError: vscode.FileSystemError = error; + assert.strictEqual(fileError.code, 'NoPermissions'); + reg.dispose(); + }); + + test('writeable file system', async function () { + const fs = new TestFS('this-fs', false); + const reg = vscode.workspace.registerFileSystemProvider(fs.scheme, fs); + let error: any | undefined; + try { + await vscode.workspace.fs.writeFile(vscode.Uri.parse('this-fs:/foo.txt'), Buffer.from('Hello World')); + } catch (e) { + error = e; + } + assert.strictEqual(vscode.workspace.fs.isWritableFileSystem('this-fs'), true); + assert.strictEqual(error, undefined); + reg.dispose(); + }); +}); diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3eb709fc8e4..3d2d8b84847 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -16,6 +16,7 @@ import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults'; import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; //#region typed options @@ -151,6 +152,10 @@ export interface IEditorOptions { * Defaults to false. */ readOnly?: boolean; + /** + * The message to display when the editor is readonly. + */ + readOnlyMessage?: IMarkdownString; /** * Should the textarea used for input use the DOM `readonly` attribute. * Defaults to false. @@ -3459,6 +3464,30 @@ class EditorRulers extends BaseEditorOption { + constructor() { + const defaults = undefined; + + super( + EditorOption.readOnlyMessage, 'readOnlyMessage', defaults + ); + } + + public validate(_input: any): IMarkdownString | undefined { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + return _input as IMarkdownString; + } +} + +//#endregion + //#region scrollbar /** @@ -5024,6 +5053,7 @@ export const enum EditorOption { quickSuggestions, quickSuggestionsDelay, readOnly, + readOnlyMessage, renameOnType, renderControlCharacters, renderFinalNewline, @@ -5530,6 +5560,7 @@ export const EditorOptions = { readOnly: register(new EditorBooleanOption( EditorOption.readOnly, 'readOnly', false, )), + readOnlyMessage: register(new ReadonlyMessage()), renameOnType: register(new EditorBooleanOption( EditorOption.renameOnType, 'renameOnType', false, { description: nls.localize('renameOnType', "Controls whether the editor auto renames on type."), markdownDeprecationMessage: nls.localize('renameOnTypeDeprecate', "Deprecated, use `editor.linkedEditing` instead.") } diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index dcd5373ca5c..6d9212c82bc 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable, IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; @@ -55,7 +56,7 @@ export interface ITextEditorModel extends IEditorModel { /** * Signals if this model is readonly or not. */ - isReadonly(): boolean; + isReadonly(): boolean | IMarkdownString; /** * The language id of the text model if known. diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 8ce9d4473ad..21e1f04541f 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -262,61 +262,62 @@ export enum EditorOption { quickSuggestions = 86, quickSuggestionsDelay = 87, readOnly = 88, - renameOnType = 89, - renderControlCharacters = 90, - renderFinalNewline = 91, - renderLineHighlight = 92, - renderLineHighlightOnlyWhenFocus = 93, - renderValidationDecorations = 94, - renderWhitespace = 95, - revealHorizontalRightPadding = 96, - roundedSelection = 97, - rulers = 98, - scrollbar = 99, - scrollBeyondLastColumn = 100, - scrollBeyondLastLine = 101, - scrollPredominantAxis = 102, - selectionClipboard = 103, - selectionHighlight = 104, - selectOnLineNumbers = 105, - showFoldingControls = 106, - showUnused = 107, - snippetSuggestions = 108, - smartSelect = 109, - smoothScrolling = 110, - stickyScroll = 111, - stickyTabStops = 112, - stopRenderingLineAfter = 113, - suggest = 114, - suggestFontSize = 115, - suggestLineHeight = 116, - suggestOnTriggerCharacters = 117, - suggestSelection = 118, - tabCompletion = 119, - tabIndex = 120, - unicodeHighlighting = 121, - unusualLineTerminators = 122, - useShadowDOM = 123, - useTabStops = 124, - wordBreak = 125, - wordSeparators = 126, - wordWrap = 127, - wordWrapBreakAfterCharacters = 128, - wordWrapBreakBeforeCharacters = 129, - wordWrapColumn = 130, - wordWrapOverride1 = 131, - wordWrapOverride2 = 132, - wrappingIndent = 133, - wrappingStrategy = 134, - showDeprecated = 135, - inlayHints = 136, - editorClassName = 137, - pixelRatio = 138, - tabFocusMode = 139, - layoutInfo = 140, - wrappingInfo = 141, - defaultColorDecorators = 142, - colorDecoratorsActivatedOn = 143 + readOnlyMessage = 89, + renameOnType = 90, + renderControlCharacters = 91, + renderFinalNewline = 92, + renderLineHighlight = 93, + renderLineHighlightOnlyWhenFocus = 94, + renderValidationDecorations = 95, + renderWhitespace = 96, + revealHorizontalRightPadding = 97, + roundedSelection = 98, + rulers = 99, + scrollbar = 100, + scrollBeyondLastColumn = 101, + scrollBeyondLastLine = 102, + scrollPredominantAxis = 103, + selectionClipboard = 104, + selectionHighlight = 105, + selectOnLineNumbers = 106, + showFoldingControls = 107, + showUnused = 108, + snippetSuggestions = 109, + smartSelect = 110, + smoothScrolling = 111, + stickyScroll = 112, + stickyTabStops = 113, + stopRenderingLineAfter = 114, + suggest = 115, + suggestFontSize = 116, + suggestLineHeight = 117, + suggestOnTriggerCharacters = 118, + suggestSelection = 119, + tabCompletion = 120, + tabIndex = 121, + unicodeHighlighting = 122, + unusualLineTerminators = 123, + useShadowDOM = 124, + useTabStops = 125, + wordBreak = 126, + wordSeparators = 127, + wordWrap = 128, + wordWrapBreakAfterCharacters = 129, + wordWrapBreakBeforeCharacters = 130, + wordWrapColumn = 131, + wordWrapOverride1 = 132, + wordWrapOverride2 = 133, + wrappingIndent = 134, + wrappingStrategy = 135, + showDeprecated = 136, + inlayHints = 137, + editorClassName = 138, + pixelRatio = 139, + tabFocusMode = 140, + layoutInfo = 141, + wrappingInfo = 142, + defaultColorDecorators = 143, + colorDecoratorsActivatedOn = 144 } /** @@ -920,4 +921,4 @@ export enum WrappingIndent { * DeepIndent => wrapped lines get +2 indentation toward the parent. */ DeepIndent = 3 -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/message/browser/messageController.css b/src/vs/editor/contrib/message/browser/messageController.css index 2644813044d..c896fd4761b 100644 --- a/src/vs/editor/contrib/message/browser/messageController.css +++ b/src/vs/editor/contrib/message/browser/messageController.css @@ -37,6 +37,15 @@ border: 1px solid var(--vscode-inputValidation-infoBorder); } +.monaco-editor .monaco-editor-overlaymessage .message p { + margin-block: 0px; +} + +.monaco-editor .monaco-editor-overlaymessage .message a { + font-weight: bold; + color: var(--vscode-inputValidation-infoForeground); +} + .monaco-editor.hc-black .monaco-editor-overlaymessage .message, .monaco-editor.hc-light .monaco-editor-overlaymessage .message { border-width: 2px; diff --git a/src/vs/editor/contrib/message/browser/messageController.ts b/src/vs/editor/contrib/message/browser/messageController.ts index 20176e5221d..56853eca086 100644 --- a/src/vs/editor/contrib/message/browser/messageController.ts +++ b/src/vs/editor/contrib/message/browser/messageController.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { TimeoutTimer } from 'vs/base/common/async'; +import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./messageController'; @@ -14,9 +16,12 @@ import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution, ScrollType } from 'vs/editor/common/editorCommon'; import { PositionAffinity } from 'vs/editor/common/model'; +import { openLinkFromMarkdown } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; import * as nls from 'vs/nls'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import * as dom from 'vs/base/browser/dom'; export class MessageController implements IEditorContribution { @@ -32,10 +37,13 @@ export class MessageController implements IEditorContribution { private readonly _visible: IContextKey; private readonly _messageWidget = new MutableDisposable(); private readonly _messageListeners = new DisposableStore(); + private _message: { element: HTMLElement; dispose: () => void } | undefined; + private _focus: boolean = false; constructor( editor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IOpenerService private readonly _openerService: IOpenerService ) { this._editor = editor; @@ -43,6 +51,7 @@ export class MessageController implements IEditorContribution { } dispose(): void { + this._message?.dispose(); this._messageListeners.dispose(); this._messageWidget.dispose(); this._visible.reset(); @@ -52,20 +61,33 @@ export class MessageController implements IEditorContribution { return this._visible.get(); } - showMessage(message: string, position: IPosition): void { + showMessage(message: IMarkdownString | string, position: IPosition): void { - alert(message); + alert(isMarkdownString(message) ? message.value : message); this._visible.set(true); this._messageWidget.clear(); this._messageListeners.clear(); - this._messageWidget.value = new MessageWidget(this._editor, position, message); + this._message = isMarkdownString(message) ? renderMarkdown(message, { + actionHandler: { + callback: (url) => openLinkFromMarkdown(this._openerService, url, isMarkdownString(message) ? message.isTrusted : undefined), + disposables: this._messageListeners + }, + }) : undefined; + this._messageWidget.value = new MessageWidget(this._editor, position, typeof message === 'string' ? message : this._message!.element); // close on blur, cursor, model change, dispose - this._messageListeners.add(this._editor.onDidBlurEditorText(() => this.closeMessage())); + this._messageListeners.add(this._editor.onDidBlurEditorText(() => { + if (!this._focus) { + this.closeMessage(); + } + } + )); this._messageListeners.add(this._editor.onDidChangeCursorPosition(() => this.closeMessage())); this._messageListeners.add(this._editor.onDidDispose(() => this.closeMessage())); this._messageListeners.add(this._editor.onDidChangeModel(() => this.closeMessage())); + this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_ENTER, () => this._focus = true, true)); + this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_LEAVE, () => this._focus = false, true)); // 3sec this._messageListeners.add(new TimeoutTimer(() => this.closeMessage(), 3000)); @@ -132,7 +154,7 @@ class MessageWidget implements IContentWidget { return { dispose }; } - constructor(editor: ICodeEditor, { lineNumber, column }: IPosition, text: string) { + constructor(editor: ICodeEditor, { lineNumber, column }: IPosition, text: HTMLElement | string) { this._editor = editor; this._editor.revealLinesInCenterIfOutsideViewport(lineNumber, lineNumber, ScrollType.Smooth); @@ -147,8 +169,13 @@ class MessageWidget implements IContentWidget { this._domNode.appendChild(anchorTop); const message = document.createElement('div'); - message.classList.add('message'); - message.textContent = text; + if (typeof text === 'string') { + message.classList.add('message'); + message.textContent = text; + } else { + text.classList.add('message'); + message.appendChild(text); + } this._domNode.appendChild(message); const anchorBottom = document.createElement('div'); diff --git a/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts b/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts index 138f8527bba..36ff95e8105 100644 --- a/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts +++ b/src/vs/editor/contrib/readOnlyMessage/browser/contribution.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { MessageController } from 'vs/editor/contrib/message/browser/messageController'; import * as nls from 'vs/nls'; @@ -24,11 +26,16 @@ export class ReadOnlyMessageController extends Disposable implements IEditorCont private _onDidAttemptReadOnlyEdit(): void { const messageController = MessageController.get(this.editor); if (messageController && this.editor.hasModel()) { - if (this.editor.isSimpleWidget) { - messageController.showMessage(nls.localize('editor.simple.readonly', "Cannot edit in read-only input"), this.editor.getPosition()); - } else { - messageController.showMessage(nls.localize('editor.readonly', "Cannot edit in read-only editor"), this.editor.getPosition()); + let message = this.editor.getOptions().get(EditorOption.readOnlyMessage); + if (!message) { + if (this.editor.isSimpleWidget) { + message = new MarkdownString(nls.localize('editor.simple.readonly', "Cannot edit in read-only input")); + } else { + message = new MarkdownString(nls.localize('editor.readonly', "Cannot edit in read-only editor")); + } } + + messageController.showMessage(message, this.editor.getPosition()); } } } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2dc5724bd31..1e8f161471c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3309,6 +3309,10 @@ declare namespace monaco.editor { * Defaults to false. */ readOnly?: boolean; + /** + * The message to display when the editor is readonly. + */ + readOnlyMessage?: IMarkdownString; /** * Should the textarea used for input use the DOM `readonly` attribute. * Defaults to false. @@ -4934,61 +4938,62 @@ declare namespace monaco.editor { quickSuggestions = 86, quickSuggestionsDelay = 87, readOnly = 88, - renameOnType = 89, - renderControlCharacters = 90, - renderFinalNewline = 91, - renderLineHighlight = 92, - renderLineHighlightOnlyWhenFocus = 93, - renderValidationDecorations = 94, - renderWhitespace = 95, - revealHorizontalRightPadding = 96, - roundedSelection = 97, - rulers = 98, - scrollbar = 99, - scrollBeyondLastColumn = 100, - scrollBeyondLastLine = 101, - scrollPredominantAxis = 102, - selectionClipboard = 103, - selectionHighlight = 104, - selectOnLineNumbers = 105, - showFoldingControls = 106, - showUnused = 107, - snippetSuggestions = 108, - smartSelect = 109, - smoothScrolling = 110, - stickyScroll = 111, - stickyTabStops = 112, - stopRenderingLineAfter = 113, - suggest = 114, - suggestFontSize = 115, - suggestLineHeight = 116, - suggestOnTriggerCharacters = 117, - suggestSelection = 118, - tabCompletion = 119, - tabIndex = 120, - unicodeHighlighting = 121, - unusualLineTerminators = 122, - useShadowDOM = 123, - useTabStops = 124, - wordBreak = 125, - wordSeparators = 126, - wordWrap = 127, - wordWrapBreakAfterCharacters = 128, - wordWrapBreakBeforeCharacters = 129, - wordWrapColumn = 130, - wordWrapOverride1 = 131, - wordWrapOverride2 = 132, - wrappingIndent = 133, - wrappingStrategy = 134, - showDeprecated = 135, - inlayHints = 136, - editorClassName = 137, - pixelRatio = 138, - tabFocusMode = 139, - layoutInfo = 140, - wrappingInfo = 141, - defaultColorDecorators = 142, - colorDecoratorsActivatedOn = 143 + readOnlyMessage = 89, + renameOnType = 90, + renderControlCharacters = 91, + renderFinalNewline = 92, + renderLineHighlight = 93, + renderLineHighlightOnlyWhenFocus = 94, + renderValidationDecorations = 95, + renderWhitespace = 96, + revealHorizontalRightPadding = 97, + roundedSelection = 98, + rulers = 99, + scrollbar = 100, + scrollBeyondLastColumn = 101, + scrollBeyondLastLine = 102, + scrollPredominantAxis = 103, + selectionClipboard = 104, + selectionHighlight = 105, + selectOnLineNumbers = 106, + showFoldingControls = 107, + showUnused = 108, + snippetSuggestions = 109, + smartSelect = 110, + smoothScrolling = 111, + stickyScroll = 112, + stickyTabStops = 113, + stopRenderingLineAfter = 114, + suggest = 115, + suggestFontSize = 116, + suggestLineHeight = 117, + suggestOnTriggerCharacters = 118, + suggestSelection = 119, + tabCompletion = 120, + tabIndex = 121, + unicodeHighlighting = 122, + unusualLineTerminators = 123, + useShadowDOM = 124, + useTabStops = 125, + wordBreak = 126, + wordSeparators = 127, + wordWrap = 128, + wordWrapBreakAfterCharacters = 129, + wordWrapBreakBeforeCharacters = 130, + wordWrapColumn = 131, + wordWrapOverride1 = 132, + wordWrapOverride2 = 133, + wrappingIndent = 134, + wrappingStrategy = 135, + showDeprecated = 136, + inlayHints = 137, + editorClassName = 138, + pixelRatio = 139, + tabFocusMode = 140, + layoutInfo = 141, + wrappingInfo = 142, + defaultColorDecorators = 143, + colorDecoratorsActivatedOn = 144 } export const EditorOptions: { @@ -5083,6 +5088,7 @@ declare namespace monaco.editor { quickSuggestions: IEditorOption; quickSuggestionsDelay: IEditorOption; readOnly: IEditorOption; + readOnlyMessage: IEditorOption; renameOnType: IEditorOption; renderControlCharacters: IEditorOption; renderFinalNewline: IEditorOption; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 1f051fdf5e0..0f15e61fb0e 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -18,6 +18,7 @@ import { localize } from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; //#region file service & providers @@ -583,6 +584,7 @@ export const enum FileSystemProviderCapabilities { export interface IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities; + readonly readOnlyMessage?: IMarkdownString; readonly onDidChangeCapabilities: Event; readonly onDidChangeFile: Event; diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index 93f1ec92983..c7a5c6cc661 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -17,6 +17,7 @@ import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files' import { normalizeWatcherPattern } from 'vs/platform/files/common/watcher'; import { GLOBSTAR } from 'vs/base/common/glob'; import { rtrim } from 'vs/base/common/strings'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; @extHostNamedCustomer(MainContext.MainThreadFileSystem) export class MainThreadFileSystem implements MainThreadFileSystemShape { @@ -50,8 +51,8 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { this._watches.dispose(); } - async $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): Promise { - this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, handle, this._proxy)); + async $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities, readonlyMessage?: IMarkdownString): Promise { + this._fileProvider.set(handle, new RemoteFileSystemProvider(this._fileService, scheme, capabilities, readonlyMessage, handle, this._proxy)); } $unregisterProvider(handle: number): void { @@ -273,6 +274,7 @@ class RemoteFileSystemProvider implements IFileSystemProviderWithFileReadWriteCa fileService: IFileService, scheme: string, capabilities: FileSystemProviderCapabilities, + public readonly readOnlyMessage: IMarkdownString | undefined, private readonly _handle: number, private readonly _proxy: ExtHostFileSystemShape ) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 60bce615c5a..9f3f6745c77 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1236,7 +1236,7 @@ export interface IFileChangeDto { } export interface MainThreadFileSystemShape extends IDisposable { - $registerFileSystemProvider(handle: number, scheme: string, capabilities: files.FileSystemProviderCapabilities): Promise; + $registerFileSystemProvider(handle: number, scheme: string, capabilities: files.FileSystemProviderCapabilities, readonlyMessage?: IMarkdownString): Promise; $unregisterProvider(handle: number): void; $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; diff --git a/src/vs/workbench/api/common/extHostFileSystem.ts b/src/vs/workbench/api/common/extHostFileSystem.ts index 41c1f8aa83f..6530636e45f 100644 --- a/src/vs/workbench/api/common/extHostFileSystem.ts +++ b/src/vs/workbench/api/common/extHostFileSystem.ts @@ -17,6 +17,7 @@ import { CharCode } from 'vs/base/common/charCode'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; class FsLinkProvider { @@ -128,7 +129,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { this._linkProviderRegistration?.dispose(); } - registerFileSystemProvider(extension: IExtensionDescription, scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean; isReadonly?: boolean } = {}) { + registerFileSystemProvider(extension: IExtensionDescription, scheme: string, provider: vscode.FileSystemProvider, options: { isCaseSensitive?: boolean; isReadonly?: boolean | vscode.MarkdownString } = {}) { // validate the given provider is complete ExtHostFileSystem._validateFileSystemProvider(provider); @@ -164,7 +165,20 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { capabilities += files.FileSystemProviderCapabilities.FileOpenReadWriteClose; } - this._proxy.$registerFileSystemProvider(handle, scheme, capabilities).catch(err => { + let readOnlyMessage: IMarkdownString | undefined; + if (options.isReadonly && isMarkdownString(options.isReadonly)) { + checkProposedApiEnabled(extension, 'readonlyMessage'); + readOnlyMessage = { + value: options.isReadonly.value, + isTrusted: options.isReadonly.isTrusted, + supportThemeIcons: options.isReadonly.supportThemeIcons, + supportHtml: options.isReadonly.supportHtml, + baseUri: options.isReadonly.baseUri, + uris: options.isReadonly.uris + }; + } + + this._proxy.$registerFileSystemProvider(handle, scheme, capabilities, readOnlyMessage).catch(err => { console.error(`FAILED to register filesystem provider of ${extension.identifier.value}-extension for the scheme ${scheme}`); console.error(err); }); diff --git a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts index f01bedd8f59..ef20010c48a 100644 --- a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts +++ b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts @@ -17,6 +17,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IExtUri } from 'vs/base/common/resources'; import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; /** * Base class of editors that want to store and restore view state. @@ -53,6 +54,12 @@ export abstract class AbstractEditorWithViewState extends Edit super.setEditorVisible(visible, group); } + protected readonlyValues(isReadonly: boolean | IMarkdownString | undefined): { readOnly: boolean; readOnlyMessage: IMarkdownString | undefined } { + const readOnly = !!isReadonly; + const readOnlyMessage = typeof isReadonly !== 'boolean' ? isReadonly : undefined; + return { readOnly, readOnlyMessage }; + } + private onWillCloseEditor(e: IEditorCloseEvent): void { const editor = e.editor; if (editor === this.input) { diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index e5918fb80ca..16447fd2fb8 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -160,7 +160,7 @@ export class TextDiffEditor extends AbstractTextEditor imp // a resolved model might have more specific information about being // readonly or not that the input did not have. control.updateOptions({ - readOnly: resolvedDiffEditorModel.modifiedModel?.isReadonly(), + ...this.readonlyValues(resolvedDiffEditorModel.modifiedModel?.isReadonly()), originalEditable: !resolvedDiffEditorModel.originalModel?.isReadonly() }); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 55daebcacc1..af0fb470503 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -144,13 +144,11 @@ export abstract class AbstractTextEditor extends Abs } protected getConfigurationOverrides(): ICodeEditorOptions { - const readOnly = this.input?.hasCapability(EditorInputCapabilities.Readonly); - return { overviewRulerLanes: 3, lineNumbersMinChars: 3, fixedOverflowWidgets: true, - readOnly, + ...this.readonlyValues(this.input?.isReadonly()), renderValidationDecorations: 'on' // render problems even in readonly editors (https://github.com/microsoft/vscode/issues/89057) }; } diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index e33f2de8e4e..899287c5ae3 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -92,7 +92,7 @@ export abstract class AbstractTextResourceEditor extends AbstractTextCodeEditor< // was already asked for being readonly or not. The rationale is that // a resolved model might have more specific information about being // readonly or not that the input did not have. - control.updateOptions({ readOnly: resolvedModel.isReadonly() }); + control.updateOptions(this.readonlyValues(resolvedModel.isReadonly())); } /** diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts index b686468ec41..739c6ab9b53 100644 --- a/src/vs/workbench/common/editor/editorInput.ts +++ b/src/vs/workbench/common/editor/editorInput.ts @@ -10,6 +10,7 @@ import { firstOrDefault } from 'vs/base/common/arrays'; import { EditorInputCapabilities, Verbosity, GroupIdentifier, ISaveOptions, IRevertOptions, IMoveResult, IEditorDescriptor, IEditorPane, IUntypedEditorInput, EditorResourceAccessor, AbstractEditorInput, isEditorInput, IEditorIdentifier } from 'vs/workbench/common/editor'; import { isEqual } from 'vs/base/common/resources'; import { ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export interface IEditorCloseHandler { @@ -124,6 +125,10 @@ export abstract class EditorInput extends AbstractEditorInput { return (this.capabilities & capability) !== 0; } + isReadonly(): boolean | IMarkdownString | undefined { + return this.hasCapability(EditorInputCapabilities.Readonly); + } + /** * Returns the display name of this input. */ diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index 6ffcdc267c9..1260e678d0e 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -6,6 +6,7 @@ import { IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; /** * The base text editor model for the diff editor. It is made up of two text editor models, the original version @@ -60,7 +61,7 @@ export class TextDiffEditorModel extends DiffEditorModel { return !!this._textDiffEditorModel; } - isReadonly(): boolean { + isReadonly(): boolean | IMarkdownString { return !!this.modifiedModel && this.modifiedModel.isReadonly(); } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index a01e1b2da9d..0a2ba0af243 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -17,6 +17,7 @@ import { ILanguageDetectionService, LanguageDetectionLanguageEventSource } from import { ThrottledDelayer } from 'vs/base/common/async'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { localize } from 'vs/nls'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; /** * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. @@ -71,7 +72,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel return this.textEditorModelHandle ? this.modelService.getModel(this.textEditorModelHandle) : null; } - isReadonly(): boolean { + isReadonly(): boolean | IMarkdownString { return true; } diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 32c0e0189db..36faa2dee55 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -64,7 +64,7 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo } public isReadonly(): boolean { - return this._model.object.isReadonly(); + return !!this._model.object.isReadonly(); } public get backupId() { diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index 016ca6bcb03..6056a86eb64 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -25,6 +25,7 @@ import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { isConfigured } from 'vs/platform/configuration/common/configuration'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; const enum ForceOpenAs { None, @@ -192,6 +193,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements return this.preferredName; } + override isReadonly(): boolean | IMarkdownString | undefined { + return this.model ? this.model.isReadonly() : super.isReadonly(); + } + override getDescription(verbosity?: Verbosity): string | undefined { return this.preferredDescription || super.getDescription(verbosity); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index 0fb252b057f..cc17c775855 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -147,7 +147,7 @@ export class TextFileEditor extends AbstractTextCodeEditor // was already asked for being readonly or not. The rationale is that // a resolved model might have more specific information about being // readonly or not that the input did not have. - control.updateOptions({ readOnly: textFileModel.isReadonly() }); + control.updateOptions(this.readonlyValues(textFileModel.isReadonly())); } catch (error) { await this.handleSetInputError(error, input, options); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 1f0858000f2..30117c116d8 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -149,7 +149,7 @@ export class ExplorerItem { } get isReadonly(): boolean { - return this.filesConfigService.isReadonly(this.resource, { resource: this.resource, name: this.name, readonly: this._readonly, locked: this._locked }); + return !!this.filesConfigService.isReadonly(this.resource, { resource: this.resource, name: this.name, readonly: this._readonly, locked: this._locked }); } get mtime(): number | undefined { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 113e26d2a2f..0bfb59ab5fc 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -795,7 +795,7 @@ export interface INotebookEditorModel extends IEditorModel { readonly notebook: INotebookTextModel | undefined; isResolved(): this is IResolvedNotebookEditorModel; isDirty(): boolean; - isReadonly(): boolean; + isReadonly(): boolean | IMarkdownString; isOrphaned(): boolean; hasAssociatedFilePath(): boolean; load(options?: INotebookLoadOptions): Promise; diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 49e39bdc34c..f6defea4059 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -7,6 +7,7 @@ import { VSBufferReadableStream, bufferToStream, streamToBuffer } from 'vs/base/ import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { filter } from 'vs/base/common/objects'; @@ -100,13 +101,11 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE return !SimpleNotebookEditorModel._isStoredFileWorkingCopy(this._workingCopy) && !!this._workingCopy?.hasAssociatedFilePath; } - isReadonly(): boolean { + isReadonly(): boolean | IMarkdownString { if (SimpleNotebookEditorModel._isStoredFileWorkingCopy(this._workingCopy)) { return this._workingCopy?.isReadonly(); - } else if (this._filesConfigurationService.isReadonly(this.resource)) { - return true; } else { - return false; + return this._filesConfigurationService.isReadonly(this.resource); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index f211ecf711f..0c7d40f6e97 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -69,6 +69,7 @@ export const allApiProposals = Object.freeze({ quickPickItemIcon: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemIcon.d.ts', quickPickItemTooltip: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', + readonlyMessage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', saveEditor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.saveEditor.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', diff --git a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts index a4bf33c5411..fb8ee256c5c 100644 --- a/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts +++ b/src/vs/workbench/services/filesConfiguration/common/filesConfigurationService.ts @@ -20,6 +20,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ResourceMap } from 'vs/base/common/map'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; export const AutoSaveAfterShortDelayContext = new RawContextKey('autoSaveAfterShortDelayContext', false, true); @@ -59,7 +60,7 @@ export interface IFilesConfigurationService { readonly onReadonlyChange: Event; - isReadonly(resource: URI, stat?: IBaseFileStat): boolean; + isReadonly(resource: URI, stat?: IBaseFileStat): boolean | IMarkdownString; updateReadonly(resource: URI, readonly: true | false | 'toggle' | 'reset'): Promise; @@ -103,7 +104,7 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi private readonly readonlyExcludeMatcher = this._register(new IdleValue(() => this.createReadonlyMatcher(FILES_READONLY_EXCLUDE_CONFIG))); private configuredReadonlyFromPermissions: boolean | undefined; - private readonly sessionReadonlyOverrides = new ResourceMap(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); + private readonly sessionReadonlyOverrides = new ResourceMap(resource => this.uriIdentityService.extUri.getComparisonKey(resource)); constructor( @IContextKeyService contextKeyService: IContextKeyService, @@ -140,13 +141,13 @@ export class FilesConfigurationService extends Disposable implements IFilesConfi return matcher; } - isReadonly(resource: URI, stat?: IBaseFileStat): boolean { + isReadonly(resource: URI, stat?: IBaseFileStat): boolean | IMarkdownString { // if the entire file system provider is readonly, we respect that // and do not allow to change readonly. we take this as a hint that // the provider has no capabilities of writing. if (this.fileService.hasCapability(resource, FileSystemProviderCapabilities.Readonly)) { - return true; + return this.fileService.getProvider(resource.scheme)?.readOnlyMessage ?? true; } // session override always wins over the others diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index cc10d5e4734..e5df1583fac 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -32,6 +32,7 @@ import { extUri } from 'vs/base/common/resources'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; interface IBackupMetaData extends IWorkingCopyBackupMeta { mtime: number; @@ -1163,7 +1164,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return !!this.textEditorModel; } - override isReadonly(): boolean { + override isReadonly(): boolean | IMarkdownString { return this.filesConfigurationService.isReadonly(this.resource, this.lastResolvedFileStat); } diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index f368be8a802..1afb6d28e84 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -28,6 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; import { IResourceWorkingCopy, ResourceWorkingCopy } from 'vs/workbench/services/workingCopy/common/resourceWorkingCopy'; import { IFileWorkingCopy, IFileWorkingCopyModel, IFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/fileWorkingCopy'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; /** * Stored file specific working copy model factory. @@ -145,7 +146,7 @@ export interface IStoredFileWorkingCopy e /** * Whether the stored file working copy is readonly or not. */ - isReadonly(): boolean; + isReadonly(): boolean | IMarkdownString; } export interface IResolvedStoredFileWorkingCopy extends IStoredFileWorkingCopy { @@ -1247,7 +1248,7 @@ export class StoredFileWorkingCopy extend //#region Utilities - isReadonly(): boolean { + isReadonly(): boolean | IMarkdownString { return this.filesConfigurationService.isReadonly(this.resource, this.lastResolvedFileStat); } diff --git a/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts b/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts new file mode 100644 index 00000000000..03bad3a664b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.readonlyMessage.d.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/166971 + +declare module 'vscode' { + + export namespace workspace { + + export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean; readonly isReadonly?: boolean | MarkdownString }): Disposable; + } +}