diff options
author | Johannes <johannes.rieken@gmail.com> | 2022-06-09 10:08:20 +0300 |
---|---|---|
committer | Johannes <johannes.rieken@gmail.com> | 2022-06-09 10:08:20 +0300 |
commit | 3622010b64abbdf8bab57bc0d163f74fe10f3787 (patch) | |
tree | ed5a642f53143ce35a7cc2e2fab9fcd4f490953f | |
parent | b501e6296339a7d8338c13a85f8928fa264fc961 (diff) | |
parent | 54ae4d8ccbf4574889c24b086079a745b3debf96 (diff) |
Merge branch 'main' into joh/voluminous-lobster
78 files changed, 970 insertions, 484 deletions
diff --git a/.github/workflows/pr-chat.yml b/.github/workflows/pr-chat.yml index 13803fda778..da4538d5fd1 100644 --- a/.github/workflows/pr-chat.yml +++ b/.github/workflows/pr-chat.yml @@ -21,5 +21,6 @@ jobs: with: token: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} slack_token: ${{ secrets.SLACK_TOKEN }} + slack_user_token: ${{ secrets.SLACK_USER_TOKEN }} slack_bot_name: "VSCodeBot" notification_channel: codereview diff --git a/.vscode/launch.json b/.vscode/launch.json index dd295c02db4..236fc3de8c3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,11 +16,7 @@ "request": "attach", "restart": true, "name": "Attach to Extension Host", - // set to a large number: if there is an issue we're debugging that keeps - // the extension host from coming up, or the renderer is paused/crashes - // before it happens, developers will get an annoying alert, e.g. #126826. - // This can be set to 0 in 1.59. - "timeout": 999999999, + "timeout": 0, "port": 5870, "outFiles": [ "${workspaceFolder}/out/**/*.js", @@ -244,6 +240,7 @@ "runtimeArgs": [ "--inspect=5875", "--no-cached-data", + "--crash-reporter-directory=${workspaceFolder}/.profile-oss/crashes", // for general runtime freezes: https://github.com/microsoft/vscode/issues/127861#issuecomment-904144910 "--disable-features=CalculateNativeWinOcclusion", ], diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 1b3790c8cc9..3120d4ad8fc 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"May 2022\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"June 2022\"" }, { "kind": 1, diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 143697b0c4e..f596c6e2e1c 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce\n\n// current milestone name\n$milestone=milestone:\"May 2022\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce\n\n// current milestone name\n$milestone=milestone:\"June 2022\"" }, { "kind": 1, diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 4aaf668a6c4..271b25ad228 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -500,7 +500,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp function addConfig(configOrFn: webpack.Configuration | Function) { let config; if (typeof configOrFn === 'function') { - config = configOrFn({}, {}); + config = (configOrFn as Function)({}, {}); webpackConfigs.push(config); } else { config = configOrFn; diff --git a/extensions/git/extension.webpack.config.js b/extensions/git/extension.webpack.config.js index 5efa2052e88..3324b6c1d98 100644 --- a/extensions/git/extension.webpack.config.js +++ b/extensions/git/extension.webpack.config.js @@ -13,6 +13,7 @@ module.exports = withDefaults({ context: __dirname, entry: { main: './src/main.ts', - ['askpass-main']: './src/askpass-main.ts' + ['askpass-main']: './src/askpass-main.ts', + ['git-editor-main']: './src/git-editor-main.ts' } }); diff --git a/extensions/git/package.json b/extensions/git/package.json index e2428b5e02f..111717f86c9 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -14,6 +14,7 @@ "contribMergeEditorToolbar", "contribViewsWelcome", "scmActionButton", + "scmInput", "scmSelectedProvider", "scmValidation", "timeline" @@ -213,83 +214,99 @@ "command": "git.commit", "title": "%command.commit%", "category": "Git", - "icon": "$(check)" + "icon": "$(check)", + "enablement": "!commitInProgress" }, { "command": "git.commitStaged", "title": "%command.commitStaged%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitEmpty", "title": "%command.commitEmpty%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedSigned", "title": "%command.commitStagedSigned%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedAmend", "title": "%command.commitStagedAmend%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAll", "title": "%command.commitAll%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllSigned", "title": "%command.commitAllSigned%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllAmend", "title": "%command.commitAllAmend%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitNoVerify", "title": "%command.commitNoVerify%", "category": "Git", - "icon": "$(check)" + "icon": "$(check)", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedNoVerify", "title": "%command.commitStagedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitEmptyNoVerify", "title": "%command.commitEmptyNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedSignedNoVerify", "title": "%command.commitStagedSignedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitStagedAmendNoVerify", "title": "%command.commitStagedAmendNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllNoVerify", "title": "%command.commitAllNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllSignedNoVerify", "title": "%command.commitAllSignedNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.commitAllAmendNoVerify", "title": "%command.commitAllAmendNoVerify%", - "category": "Git" + "category": "Git", + "enablement": "!commitInProgress" }, { "command": "git.restoreCommitTemplate", @@ -2013,6 +2030,18 @@ "scope": "machine", "description": "%config.defaultCloneDirectory%" }, + "git.useEditorAsCommitInput": { + "type": "boolean", + "scope": "resource", + "description": "%config.useEditorAsCommitInput%", + "default": false + }, + "git.verboseCommit": { + "type": "boolean", + "scope": "resource", + "markdownDescription": "%config.verboseCommit%", + "default": false + }, "git.enableSmartCommit": { "type": "boolean", "scope": "resource", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index b729f821b61..5210e64de80 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -140,6 +140,8 @@ "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.ignoreRebaseWarning": "Ignores the warning when it looks like the branch might have been rebased when pulling.", "config.defaultCloneDirectory": "The default location to clone a git repository.", + "config.useEditorAsCommitInput": "Use an editor to author the commit message.", + "config.verboseCommit": "Enable verbose output when `#git.useEditorAsCommitInput#` is enabled.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", "config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.", "config.smartCommitChanges.all": "Automatically stage all changes.", diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 4b180dac920..14c7447e3e8 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -137,6 +137,8 @@ export interface CommitOptions { empty?: boolean; noVerify?: boolean; requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; } export interface FetchOptions { @@ -336,4 +338,5 @@ export const enum GitErrorCodes { PatchDoesNotApply = 'PatchDoesNotApply', NoPathFound = 'NoPathFound', UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage' } diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 2f9678c6e04..d9d9378f8aa 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -6,9 +6,8 @@ import { window, InputBoxOptions, Uri, Disposable, workspace } from 'vscode'; import { IDisposable, EmptyDisposable, toDisposable } from './util'; import * as path from 'path'; -import { IIPCHandler, IIPCServer, createIPCServer } from './ipc/ipcServer'; +import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; import { CredentialsProvider, Credentials } from './api/git'; -import { OutputChannelLogger } from './log'; export class Askpass implements IIPCHandler { @@ -16,16 +15,7 @@ export class Askpass implements IIPCHandler { private cache = new Map<string, Credentials>(); private credentialsProviders = new Set<CredentialsProvider>(); - static async create(outputChannelLogger: OutputChannelLogger, context?: string): Promise<Askpass> { - try { - return new Askpass(await createIPCServer(context)); - } catch (err) { - outputChannelLogger.logError(`Failed to create git askpass IPC: ${err}`); - return new Askpass(); - } - } - - private constructor(private ipc?: IIPCServer) { + constructor(private ipc?: IIPCServer) { if (ipc) { this.disposable = ipc.registerHandler('askpass', this); } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index cd43dc2ea6e..e3968df3a0d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1516,6 +1516,14 @@ export class CommandCenter { opts.signoff = true; } + if (config.get<boolean>('useEditorAsCommitInput')) { + opts.useEditor = true; + + if (config.get<boolean>('verboseCommit')) { + opts.verbose = true; + } + } + const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges'); if ( @@ -1563,7 +1571,7 @@ export class CommandCenter { const message = await getCommitMessage(); - if (!message && !opts.amend) { + if (!message && !opts.amend && !opts.useEditor) { return false; } @@ -1623,10 +1631,13 @@ export class CommandCenter { private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> { const message = repository.inputBox.value; + const root = Uri.file(repository.root); + const config = workspace.getConfiguration('git', root); + const getCommitMessage = async () => { let _message: string | undefined = message; - if (!_message) { + if (!_message && !config.get<boolean>('useEditorAsCommitInput')) { const value: string | undefined = undefined; if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { @@ -3010,7 +3021,7 @@ export class CommandCenter { }; let message: string; - let type: 'error' | 'warning' = 'error'; + let type: 'error' | 'warning' | 'information' = 'error'; const choices = new Map<string, () => void>(); const openOutputChannelChoice = localize('open git log', "Open Git Log"); @@ -3073,6 +3084,12 @@ export class CommandCenter { message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git."); choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-setup-git'))); break; + case GitErrorCodes.EmptyCommitMessage: + message = localize('empty commit', "Commit operation was cancelled due to empty commit message."); + choices.clear(); + type = 'information'; + options.modal = false; + break; default: { const hint = (err.stderr || err.message || String(err)) .replace(/^error: /mi, '') @@ -3094,10 +3111,20 @@ export class CommandCenter { return; } + let result: string | undefined; const allChoices = Array.from(choices.keys()); - const result = type === 'error' - ? await window.showErrorMessage(message, options, ...allChoices) - : await window.showWarningMessage(message, options, ...allChoices); + + switch (type) { + case 'error': + result = await window.showErrorMessage(message, options, ...allChoices); + break; + case 'warning': + result = await window.showWarningMessage(message, options, ...allChoices); + break; + case 'information': + result = await window.showInformationMessage(message, options, ...allChoices); + break; + } if (result) { const resultFn = choices.get(result); diff --git a/extensions/git/src/git-editor-empty.sh b/extensions/git/src/git-editor-empty.sh new file mode 100755 index 00000000000..1a2485251c3 --- /dev/null +++ b/extensions/git/src/git-editor-empty.sh @@ -0,0 +1 @@ +#!/bin/sh diff --git a/extensions/git/src/git-editor-main.ts b/extensions/git/src/git-editor-main.ts new file mode 100644 index 00000000000..eb4da4a40b5 --- /dev/null +++ b/extensions/git/src/git-editor-main.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IPCClient } from './ipc/ipcClient'; + +function fatal(err: any): void { + console.error(err); + process.exit(1); +} + +function main(argv: string[]): void { + const ipcClient = new IPCClient('git-editor'); + const commitMessagePath = argv[argv.length - 1]; + + ipcClient.call({ commitMessagePath }).then(() => { + setTimeout(() => process.exit(0), 0); + }).catch(err => fatal(err)); +} + +main(process.argv); diff --git a/extensions/git/src/git-editor.sh b/extensions/git/src/git-editor.sh new file mode 100755 index 00000000000..1c45c2deac1 --- /dev/null +++ b/extensions/git/src/git-editor.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +ELECTRON_RUN_AS_NODE="1" \ +"$VSCODE_GIT_EDITOR_NODE" "$VSCODE_GIT_EDITOR_MAIN" $VSCODE_GIT_EDITOR_EXTRA_ARGS $@ diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 203d6949897..5e7ddef8599 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1400,20 +1400,37 @@ export class Repository { } async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise<void> { - const args = ['commit', '--quiet', '--allow-empty-message']; + const args = ['commit', '--quiet']; + const options: SpawnOptions = {}; + + if (message) { + options.input = message; + args.push('--file', '-'); + } + + if (opts.verbose) { + args.push('--verbose'); + } if (opts.all) { args.push('--all'); } - if (opts.amend && message) { + if (opts.amend) { args.push('--amend'); } - if (opts.amend && !message) { - args.push('--amend', '--no-edit'); - } else { - args.push('--file', '-'); + if (!opts.useEditor) { + if (!message) { + if (opts.amend) { + args.push('--no-edit'); + } else { + options.input = ''; + args.push('--file', '-'); + } + } + + args.push('--allow-empty-message'); } if (opts.signoff) { @@ -1438,7 +1455,7 @@ export class Repository { } try { - await this.exec(args, !opts.amend || message ? { input: message || '' } : {}); + await this.exec(args, options); } catch (commitErr) { await this.handleCommitError(commitErr); } @@ -1462,6 +1479,9 @@ export class Repository { if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; throw commitErr; + } else if (/Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { + commitErr.gitErrorCode = GitErrorCodes.EmptyCommitMessage; + throw commitErr; } try { diff --git a/extensions/git/src/gitEditor.ts b/extensions/git/src/gitEditor.ts new file mode 100644 index 00000000000..5f65a7dbcf2 --- /dev/null +++ b/extensions/git/src/gitEditor.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; +import { TabInputText, Uri, window, workspace } from 'vscode'; +import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; +import { EmptyDisposable, IDisposable } from './util'; + +interface GitEditorRequest { + commitMessagePath?: string; +} + +export class GitEditor implements IIPCHandler { + + private disposable: IDisposable = EmptyDisposable; + + constructor(private ipc?: IIPCServer) { + if (ipc) { + this.disposable = ipc.registerHandler('git-editor', this); + } + } + + async handle({ commitMessagePath }: GitEditorRequest): Promise<any> { + if (commitMessagePath) { + const uri = Uri.file(commitMessagePath); + const doc = await workspace.openTextDocument(uri); + await window.showTextDocument(doc, { preview: false }); + + return new Promise((c) => { + const onDidClose = window.tabGroups.onDidChangeTabs(async (tabs) => { + if (tabs.closed.some(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString())) { + onDidClose.dispose(); + return c(true); + } + }); + }); + } + } + + getEnv(): { [key: string]: string } { + if (!this.ipc) { + return { + GIT_EDITOR: `"${path.join(__dirname, 'git-editor-empty.sh')}"` + }; + } + + let env: { [key: string]: string } = { + VSCODE_GIT_EDITOR_NODE: process.execPath, + VSCODE_GIT_EDITOR_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', + VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'git-editor-main.js') + }; + + const config = workspace.getConfiguration('git'); + if (config.get<boolean>('useEditorAsCommitInput')) { + env.GIT_EDITOR = `"${path.join(__dirname, 'git-editor.sh')}"`; + } + + return env; + } + + dispose(): void { + this.disposable.dispose(); + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index a5e7c060f00..46f612539fb 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -25,6 +25,8 @@ import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; import { OutputChannelLogger } from './log'; +import { createIPCServer, IIPCServer } from './ipc/ipcServer'; +import { GitEditor } from './gitEditor'; const deactivateTasks: { (): Promise<any> }[] = []; @@ -60,10 +62,21 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu return !skip; }); - const askpass = await Askpass.create(outputChannelLogger, context.storagePath); + let ipc: IIPCServer | undefined = undefined; + + try { + ipc = await createIPCServer(context.storagePath); + } catch (err) { + outputChannelLogger.logError(`Failed to create git IPC: ${err}`); + } + + const askpass = new Askpass(ipc); disposables.push(askpass); - const environment = askpass.getEnv(); + const gitEditor = new GitEditor(ipc); + disposables.push(gitEditor); + + const environment = { ...askpass.getEnv(), ...gitEditor.getEnv() }; const terminalEnvironmentManager = new TerminalEnvironmentManager(context, environment); disposables.push(terminalEnvironmentManager); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 69c184209fa..c6fa51b5497 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -454,6 +454,13 @@ class ProgressManager { const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root))); onDidChange(_ => this.updateEnablement()); this.updateEnablement(); + + this.repository.onDidChangeOperations(() => { + const commitInProgress = this.repository.operations.isRunning(Operation.Commit); + + this.repository.sourceControl.inputBox.enabled = !commitInProgress; + commands.executeCommand('setContext', 'commitInProgress', commitInProgress); + }); } private updateEnablement(): void { diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index 13997275056..1f1c02d3356 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -12,6 +12,7 @@ "../../src/vscode-dts/vscode.d.ts", "../../src/vscode-dts/vscode.proposed.diffCommand.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmInput.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmValidation.d.ts", "../../src/vscode-dts/vscode.proposed.tabs.d.ts", diff --git a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts index d9e939b463c..c36403d06ee 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts @@ -4,23 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { tryInsertUriList } from './dropIntoEditor'; +import { tryGetUriListSnippet } from './dropIntoEditor'; export function registerPasteProvider(selector: vscode.DocumentSelector) { return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider { async provideDocumentPasteEdits( document: vscode.TextDocument, - range: vscode.Range, + _ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken, - ): Promise<vscode.SnippetTextEdit | undefined> { + ): Promise<vscode.DocumentPasteEdit | undefined> { const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', false); if (!enabled) { return; } - return tryInsertUriList(document, range, dataTransfer, token); + const snippet = await tryGetUriListSnippet(document, dataTransfer, token); + if (snippet) { + return { insertText: snippet }; + } + return undefined; } + }, { + pasteMimeTypes: ['text/uri-list'] }); } diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index 2ad71ec0516..01daa70085f 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -25,19 +25,22 @@ const imageFileExtensions = new Set<string>([ export function registerDropIntoEditor(selector: vscode.DocumentSelector) { return vscode.languages.registerDocumentOnDropEditProvider(selector, new class implements vscode.DocumentOnDropEditProvider { - async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> { + async provideDocumentOnDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> { const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true); if (!enabled) { - return; + return undefined; } - const replacementRange = new vscode.Range(position, position); - return tryInsertUriList(document, replacementRange, dataTransfer, token); + const snippet = await tryGetUriListSnippet(document, dataTransfer, token); + if (snippet) { + return { insertText: snippet }; + } + return undefined; } }); } -export async function tryInsertUriList(document: vscode.TextDocument, replacementRange: vscode.Range, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetTextEdit | undefined> { +export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.SnippetString | undefined> { const urlList = await dataTransfer.get('text/uri-list')?.asString(); if (!urlList || token.isCancellationRequested) { return undefined; @@ -72,5 +75,5 @@ export async function tryInsertUriList(document: vscode.TextDocument, replacemen } }); - return new vscode.SnippetTextEdit(replacementRange, snippet); + return snippet; } diff --git a/extensions/markdown-language-features/src/languageFeatures/rename.ts b/extensions/markdown-language-features/src/languageFeatures/rename.ts index db06b7eeb9c..955581b9d97 100644 --- a/extensions/markdown-language-features/src/languageFeatures/rename.ts +++ b/extensions/markdown-language-features/src/languageFeatures/rename.ts @@ -142,7 +142,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide return this.renameExternalLink(allRefsInfo, newName); } else if (triggerRef.kind === 'header' || (triggerRef.kind === 'link' && triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'definition' || triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal'))) { return this.renameFragment(allRefsInfo, newName); - } else if (triggerRef.kind === 'link' && !triggerRef.link.source.fragmentRange?.contains(position) && triggerRef.link.kind === 'link' && triggerRef.link.href.kind === 'internal') { + } else if (triggerRef.kind === 'link' && !triggerRef.link.source.fragmentRange?.contains(position) && (triggerRef.link.kind === 'link' || triggerRef.link.kind === 'definition') && triggerRef.link.href.kind === 'internal') { return this.renameFilePath(triggerRef.link.source.resource, triggerRef.link.href, allRefsInfo, newName); } diff --git a/extensions/markdown-language-features/src/test/rename.test.ts b/extensions/markdown-language-features/src/test/rename.test.ts index 53839071ea4..6560055823d 100644 --- a/extensions/markdown-language-features/src/test/rename.test.ts +++ b/extensions/markdown-language-features/src/test/rename.test.ts @@ -614,4 +614,79 @@ suite('markdown: rename', () => { ] }); }); + + test('Rename on definition path should update all references to path', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `[ref text][ref]`, + `[direct](/file)`, + `[ref]: /file`, // rename here + )); + + const workspace = new InMemoryWorkspaceMarkdownDocuments([doc]); + + const preparedInfo = await prepareRename(doc, new vscode.Position(2, 10), workspace); + assert.strictEqual(preparedInfo!.placeholder, '/file'); + assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); + + const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "/newFile", workspace); + assertEditsEqual(edit!, { + uri, edits: [ + new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/newFile'), + new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/newFile'), + ] + }); + }); + + test('Rename on definition path where file exists should also update file', async () => { + const uri1 = workspacePath('doc.md'); + const doc1 = new InMemoryDocument(uri1, joinLines( + `[ref text][ref]`, + `[direct](/doc2)`, + `[ref]: /doc2`, // rename here + )); + + const uri2 = workspacePath('doc2.md'); + const doc2 = new InMemoryDocument(uri2, joinLines()); + + const workspace = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); + + const preparedInfo = await prepareRename(doc1, new vscode.Position(2, 10), workspace); + assert.strictEqual(preparedInfo!.placeholder, '/doc2'); + assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12)); + + const edit = await getRenameEdits(doc1, new vscode.Position(2, 10), "/new-doc", workspace); + assertEditsEqual(edit!, { + uri: uri1, edits: [ + new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/new-doc'), + new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/new-doc'), + ] + }, { + originalUri: uri2, + newUri: workspacePath('new-doc.md') + }); + }); + + test('Rename on definition path header should update all references to header', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `[ref text][ref]`, + `[direct](/file#header)`, + `[ref]: /file#header`, // rename here + )); + + const workspace = new InMemoryWorkspaceMarkdownDocuments([doc]); + + const preparedInfo = await prepareRename(doc, new vscode.Position(2, 16), workspace); + assert.strictEqual(preparedInfo!.placeholder, 'header'); + assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 13, 2, 19)); + + const edit = await getRenameEdits(doc, new vscode.Position(2, 16), "New Header", workspace); + assertEditsEqual(edit!, { + uri, edits: [ + new vscode.TextEdit(new vscode.Range(1, 15, 1, 21), 'new-header'), + new vscode.TextEdit(new vscode.Range(2, 13, 2, 19), 'new-header'), + ] + }); + }); }); diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index dc28ed7f4ed..676914a2dfb 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -12,10 +12,12 @@ import * as path from 'path'; import * as fs from 'fs'; import * as minimatch from 'minimatch'; import * as nls from 'vscode-nls'; +import { Utils } from 'vscode-uri'; import { findPreferredPM } from './preferred-pm'; import { readScripts } from './readScripts'; const localize = nls.loadMessageBundle(); +const excludeRegex = new RegExp('^(node_modules|.vscode-test)$', 'i'); export interface INpmTaskDefinition extends TaskDefinition { script: string; @@ -156,7 +158,7 @@ export async function hasNpmScripts(): Promise<boolean> { } try { for (const folder of folders) { - if (isAutoDetectionEnabled(folder)) { + if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) { const relativePattern = new RelativePattern(folder, '**/package.json'); const paths = await workspace.findFiles(relativePattern, '**/node_modules/**'); if (paths.length > 0) { @@ -182,7 +184,7 @@ async function detectNpmScripts(context: ExtensionContext, showWarning: boolean) } try { for (const folder of folders) { - if (isAutoDetectionEnabled(folder)) { + if (isAutoDetectionEnabled(folder) && !excludeRegex.test(Utils.basename(folder.uri))) { const relativePattern = new RelativePattern(folder, '**/package.json'); const paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**'); for (const path of paths) { @@ -206,6 +208,9 @@ export async function detectNpmScriptsForFolder(context: ExtensionContext, folde const folderTasks: IFolderTaskItem[] = []; try { + if (excludeRegex.test(Utils.basename(folder))) { + return folderTasks; + } const relativePattern = new RelativePattern(folder.fsPath, '**/package.json'); const paths = await workspace.findFiles(relativePattern, '**/node_modules/**'); diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json index 70b57d594fe..b37fd6b0083 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json @@ -65,7 +65,7 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": ["meta.embedded", "source.groovy.embedded", "meta.jsx.children"], "settings": { //"background": "#002451", "foreground": "#FFFFFF" diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index c35aa35aee0..bc0ac10388b 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -174,6 +174,7 @@ export default class FileConfigurationManager extends Disposable { document); const preferences: Proto.UserPreferences = { + ...config.get('unstable'), quotePreference: this.getQuoteStylePreference(preferencesConfig), importModuleSpecifierPreference: getImportModuleSpecifierPreference(preferencesConfig), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts index 44359de5e84..b73025ab2e7 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/ipynb.test.ts @@ -18,9 +18,9 @@ import * as vscode from 'vscode'; const notebookEditor = vscode.window.activeNotebookEditor; assert.ok(notebookEditor); - assert.strictEqual(notebookEditor.document.cellCount, 2); - assert.strictEqual(notebookEditor.document.cellAt(0).kind, vscode.NotebookCellKind.Markup); - assert.strictEqual(notebookEditor.document.cellAt(1).kind, vscode.NotebookCellKind.Code); - assert.strictEqual(notebookEditor.document.cellAt(1).outputs.length, 1); + assert.strictEqual(notebookEditor.notebook.cellCount, 2); + assert.strictEqual(notebookEditor.notebook.cellAt(0).kind, vscode.NotebookCellKind.Markup); + assert.strictEqual(notebookEditor.notebook.cellAt(1).kind, vscode.NotebookCellKind.Code); + assert.strictEqual(notebookEditor.notebook.cellAt(1).outputs.length, 1); }); }); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 21b798dfbfd..12b79163769 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -75,11 +75,11 @@ import * as utils from '../utils'; const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const editor = await vscode.window.showNotebookDocument(uri); - assert.strictEqual(uri.toString(), editor.document.uri.toString()); + assert.strictEqual(uri.toString(), editor.notebook.uri.toString()); - assert.strictEqual(notebookDocumentsFromOnDidOpen.has(editor.document), true); + assert.strictEqual(notebookDocumentsFromOnDidOpen.has(editor.notebook), true); - const includes = vscode.workspace.notebookDocuments.includes(editor.document); + const includes = vscode.workspace.notebookDocuments.includes(editor.notebook); assert.strictEqual(true, includes); sub.dispose(); @@ -104,7 +104,7 @@ import * as utils from '../utils'; const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const editor = await vscode.window.showNotebookDocument(resource); assert.ok(await openedEditor); - assert.strictEqual(editor.document.uri.toString(), resource.toString()); + assert.strictEqual(editor.notebook.uri.toString(), resource.toString()); }); test('Active/Visible Editor', async function () { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index f863bf014b6..c4d4733578a 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -77,7 +77,7 @@ export class Kernel { function getFocusedCell(editor?: vscode.NotebookEditor) { - return editor ? editor.document.cellAt(editor.selections[0].start) : undefined; + return editor ? editor.notebook.cellAt(editor.selections[0].start) : undefined; } async function assertKernel(kernel: Kernel, notebook: vscode.NotebookDocument): Promise<void> { @@ -204,12 +204,12 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const cellsChangeEvent = asPromise<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); const cellChangeEventRet = await cellsChangeEvent; - assert.strictEqual(cellChangeEventRet.notebook, editor.document); + assert.strictEqual(cellChangeEventRet.notebook, editor.notebook); assert.strictEqual(cellChangeEventRet.contentChanges.length, 1); assert.deepStrictEqual(cellChangeEventRet.contentChanges[0], <vscode.NotebookDocumentContentChange>{ range: new vscode.NotebookRange(1, 1), removedCells: [], - addedCells: [editor.document.cellAt(1)] + addedCells: [editor.notebook.cellAt(1)] }); const moveCellEvent = asPromise<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument); @@ -220,11 +220,11 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('notebook.cell.execute'); const cellOutputsAddedRet = await cellOutputChange; - assert.strictEqual(cellOutputsAddedRet.notebook.uri.toString(), editor.document.uri.toString()); + assert.strictEqual(cellOutputsAddedRet.notebook.uri.toString(), editor.notebook.uri.toString()); assert.strictEqual(cellOutputsAddedRet.metadata, undefined); assert.strictEqual(cellOutputsAddedRet.contentChanges.length, 0); assert.strictEqual(cellOutputsAddedRet.cellChanges.length, 1); - assert.deepStrictEqual(cellOutputsAddedRet.cellChanges[0].cell, editor.document.cellAt(0)); + assert.deepStrictEqual(cellOutputsAddedRet.cellChanges[0].cell, editor.notebook.cellAt(0)); assert.deepStrictEqual(cellOutputsAddedRet.cellChanges[0].executionSummary, { executionOrder: undefined, success: undefined, timing: undefined }); // TODO@jrieken should this be undefined instead all empty? assert.strictEqual(cellOutputsAddedRet.cellChanges[0].document, undefined); assert.strictEqual(cellOutputsAddedRet.cellChanges[0].metadata, undefined); @@ -235,15 +235,15 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('notebook.cell.clearOutputs'); const cellOutputsCleardRet = await cellOutputClear; assert.deepStrictEqual(cellOutputsCleardRet, <vscode.NotebookDocumentChangeEvent>{ - notebook: editor.document, + notebook: editor.notebook, metadata: undefined, contentChanges: [], cellChanges: [{ - cell: editor.document.cellAt(0), + cell: editor.notebook.cellAt(0), document: undefined, executionSummary: undefined, metadata: undefined, - outputs: editor.document.cellAt(0).outputs + outputs: editor.notebook.cellAt(0).outputs }], }); assert.strictEqual(cellOutputsCleardRet.cellChanges[0].cell.outputs.length, 0); @@ -289,21 +289,21 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const editor = await vscode.window.showNotebookDocument(notebook); const notebookChangeEvent = asPromise<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument); - const version = editor.document.version; + const version = editor.notebook.version; await editor.edit(editBuilder => { editBuilder.replaceCells(1, 0, [{ kind: vscode.NotebookCellKind.Code, languageId: 'javascript', value: 'test 2', outputs: [], metadata: undefined }]); editBuilder.replaceCellMetadata(0, { inputCollapsed: false }); }); await notebookChangeEvent; - assert.strictEqual(editor.document.cellCount, 3); - assert.strictEqual(editor.document.cellAt(0)?.metadata.inputCollapsed, false); - assert.strictEqual(version + 1, editor.document.version); + assert.strictEqual(editor.notebook.cellCount, 3); + assert.strictEqual(editor.notebook.cellAt(0)?.metadata.inputCollapsed, false); + assert.strictEqual(version + 1, editor.notebook.version); await vscode.commands.executeCommand('undo'); - assert.strictEqual(version + 2, editor.document.version); - assert.strictEqual(editor.document.cellAt(0)?.metadata.inputCollapsed, undefined); - assert.strictEqual(editor.document.cellCount, 2); + assert.strictEqual(version + 2, editor.notebook.version); + assert.strictEqual(editor.notebook.cellAt(0)?.metadata.inputCollapsed, undefined); + assert.strictEqual(editor.notebook.cellCount, 2); }); // #126371 @@ -326,7 +326,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); - const secondCell = editor.document.cellAt(1); + const secondCell = editor.notebook.cellAt(1); assert.strictEqual(secondCell.outputs.length, 1); assert.deepStrictEqual(secondCell.outputs[0].metadata, { testOutputMetadata: true, ['text/plain']: { testOutputItemMetadata: true } }); assert.strictEqual(secondCell.outputs[0].items.length, 1); @@ -353,22 +353,22 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { let activeCell = getFocusedCell(editor); assert.notStrictEqual(getFocusedCell(editor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(editor.document.cellCount, 4); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(editor.notebook.cellCount, 4); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); // ---- focus bottom ---- // await vscode.commands.executeCommand('notebook.focusBottom'); activeCell = getFocusedCell(editor); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 3); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 3); // ---- focus top and then copy down ---- // await vscode.commands.executeCommand('notebook.focusTop'); activeCell = getFocusedCell(editor); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 0); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.copyDown'); activeCell = getFocusedCell(editor); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), 'test'); { @@ -381,25 +381,25 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { } activeCell = getFocusedCell(editor); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), ''); // ---- focus top and then copy up ---- // await vscode.commands.executeCommand('notebook.focusTop'); await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.strictEqual(editor.document.cellCount, 5); - assert.strictEqual(editor.document.cellAt(0).document.getText(), 'test'); - assert.strictEqual(editor.document.cellAt(1).document.getText(), 'test'); - assert.strictEqual(editor.document.cellAt(2).document.getText(), ''); - assert.strictEqual(editor.document.cellAt(3).document.getText(), ''); + assert.strictEqual(editor.notebook.cellCount, 5); + assert.strictEqual(editor.notebook.cellAt(0).document.getText(), 'test'); + assert.strictEqual(editor.notebook.cellAt(1).document.getText(), 'test'); + assert.strictEqual(editor.notebook.cellAt(2).document.getText(), ''); + assert.strictEqual(editor.notebook.cellAt(3).document.getText(), ''); activeCell = getFocusedCell(editor); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 0); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 0); // ---- move up and down ---- // await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.strictEqual(editor.document.getCells().indexOf(getFocusedCell(editor)!), 1, + assert.strictEqual(editor.notebook.getCells().indexOf(getFocusedCell(editor)!), 1, `first move down, active cell ${getFocusedCell(editor)!.document.uri.toString()}, ${getFocusedCell(editor)!.document.getText()}`); await vscode.commands.executeCommand('workbench.action.files.save'); @@ -427,7 +427,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // const editor = vscode.window.activeNotebookEditor!; - // const cell = editor.document.cellAt(0); + // const cell = editor.notebook.cellAt(0); // assert.strictEqual(cell.outputs.length, 0); // currentKernelProvider.setHasKernels(false); @@ -452,7 +452,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); await vscode.commands.executeCommand('notebook.execute'); assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work @@ -463,7 +463,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); await withEvent(vscode.workspace.onDidChangeNotebookDocument, async (event) => { await vscode.commands.executeCommand('notebook.execute'); @@ -484,7 +484,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + assert.strictEqual(vscode.window.activeNotebookEditor?.notebook.uri.fsPath, secondResource.fsPath); }); }); @@ -524,7 +524,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); await withEvent<vscode.NotebookDocumentChangeEvent>(vscode.workspace.onDidChangeNotebookDocument, async (event) => { await vscode.commands.executeCommand('notebook.execute'); @@ -544,7 +544,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('notebook.execute', resource); await event; assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + assert.strictEqual(vscode.window.activeNotebookEditor?.notebook.uri.fsPath, secondResource.fsPath); }); }); @@ -553,7 +553,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const editor = await vscode.window.showNotebookDocument(notebook); assert.strictEqual(vscode.window.activeNotebookEditor === editor, true, 'notebook first'); - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); const alternativeKernel = new class extends Kernel { constructor() { @@ -597,7 +597,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); vscode.commands.executeCommand('notebook.cell.execute'); let eventCount = 0; @@ -636,8 +636,8 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const activeCell = getFocusedCell(editor); assert.notStrictEqual(getFocusedCell(editor), undefined); assert.strictEqual(activeCell!.document.getText(), ''); - assert.strictEqual(editor.document.cellCount, 4); - assert.strictEqual(editor.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(editor.notebook.cellCount, 4); + assert.strictEqual(editor.notebook.getCells().indexOf(activeCell!), 1); await withEvent(vscode.workspace.onDidChangeTextDocument, async event => { const edit = new vscode.WorkspaceEdit(); @@ -645,7 +645,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.workspace.applyEdit(edit); await event; assert.strictEqual(vscode.window.activeNotebookEditor === editor, true); - assert.deepStrictEqual(editor.document.cellAt(1), getFocusedCell(editor)); + assert.deepStrictEqual(editor.notebook.cellAt(1), getFocusedCell(editor)); assert.strictEqual(getFocusedCell(editor)?.document.getText(), 'var abc = 0;'); }); }); @@ -667,8 +667,8 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // make sure that the previous dirty editor is still restored in the extension host and no data loss assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 4); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 4); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); }); @@ -692,15 +692,15 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // switch to the first editor await vscode.window.showNotebookDocument(notebook); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 4); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 4); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'var abc = 0;'); // switch to the second editor await vscode.window.showNotebookDocument(secondNotebook); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); - assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cellCount, 3); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellAt(1), getFocusedCell(vscode.window.activeNotebookEditor)); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.notebook.cellCount, 3); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); }); @@ -721,7 +721,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor!)?.document.languageId, 'typescript'); assert.notStrictEqual(firstNotebookEditor, secondNotebookEditor); - assert.strictEqual(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); + assert.strictEqual(firstNotebookEditor?.notebook, secondNotebookEditor?.notebook, 'split notebook editors share the same document'); }); @@ -737,7 +737,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // await vscode.commands.executeCommand('vscode.open', cell.document.uri, vscode.ViewColumn.Active); assert.strictEqual(!!vscode.window.activeNotebookEditor, true); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), document.uri.toString()); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); }); test('Cannot open notebook from cell-uri with vscode.open-command', async function () { @@ -752,7 +752,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { // removes the fragment if it matches something numeric. For notebooks that's not wanted... await vscode.commands.executeCommand('vscode.open', cell.document.uri); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.uri.toString(), document.uri.toString()); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.uri.toString(), document.uri.toString()); }); test('#97830, #97764. Support switch to other editor types', async function () { @@ -805,15 +805,15 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { await vscode.commands.executeCommand('notebook.cell.copyDown'); await vscode.commands.executeCommand('notebook.cell.edit'); activeCell = getFocusedCell(vscode.window.activeNotebookEditor); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().indexOf(activeCell!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.getCells().indexOf(activeCell!), 1); assert.strictEqual(activeCell?.document.getText(), 'test'); const edit = new vscode.WorkspaceEdit(); edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - assert.strictEqual(vscode.window.activeNotebookEditor!.document.getCells().length, 3); - assert.notStrictEqual(vscode.window.activeNotebookEditor!.document.cellAt(0).document.getText(), vscode.window.activeNotebookEditor!.document.cellAt(1).document.getText()); + assert.strictEqual(vscode.window.activeNotebookEditor!.notebook.getCells().length, 3); + assert.notStrictEqual(vscode.window.activeNotebookEditor!.notebook.cellAt(0).document.getText(), vscode.window.activeNotebookEditor!.notebook.cellAt(1).document.getText()); await closeAllEditors(); }); @@ -876,7 +876,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; - const cell = editor.document.cellAt(0); + const cell = editor.notebook.cellAt(0); assert.strictEqual(cell.executionSummary?.success, undefined); assert.strictEqual(cell.executionSummary?.executionOrder, undefined); @@ -999,7 +999,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = { const notebook = await openRandomNotebookDocument(); const editor = await vscode.window.showNotebookDocument(notebook); - assert.strictEqual(editor.document.metadata.custom?.testMetadata, false); + assert.strictEqual(editor.notebook.metadata.custom?.testMetadata, false); assert.strictEqual(getFocusedCell(editor)?.metadata.custom?.testCellMetadata, 123); assert.strictEqual(getFocusedCell(editor)?.document.languageId, 'typescript'); }); diff --git a/package.json b/package.json index 9ac0994ecec..cc87e68f1eb 100644 --- a/package.json +++ b/package.json @@ -200,7 +200,7 @@ "style-loader": "^1.0.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220518", + "typescript": "^4.8.0-dev.20220608", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 6ad570fcd54..bb5e3134ec5 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -724,10 +724,21 @@ export interface CodeActionProvider { /** * @internal */ +export interface DocumentPasteEdit { + insertText: string | { snippet: string }; + additionalEdit?: WorkspaceEdit; +} + +/** + * @internal + */ export interface DocumentPasteEditProvider { - prepareDocumentPaste?(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<undefined | VSDataTransfer>; - provideDocumentPasteEdits(model: model.ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<WorkspaceEdit | SnippetTextEdit | undefined>; + readonly pasteMimeTypes: readonly string[]; + + prepareDocumentPaste?(model: model.ITextModel, selections: readonly Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<undefined | VSDataTransfer>; + + provideDocumentPasteEdits(model: model.ITextModel, selections: readonly Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<DocumentPasteEdit | undefined>; } /** @@ -1123,11 +1134,6 @@ export interface DocumentSymbolProvider { export type TextEdit = { range: IRange; text: string; eol?: model.EndOfLineSequence }; -export interface SnippetTextEdit { - range: IRange; - snippet: string; -} - /** * Interface used to format a model */ @@ -1800,10 +1806,17 @@ export enum ExternalUriOpenerPriority { Preferred = 3, } +/** + * @internal + */ +export interface DocumentOnDropEdit { + insertText: string | { snippet: string }; + additionalEdit?: WorkspaceEdit; +} /** * @internal */ export interface DocumentOnDropEditProvider { - provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<SnippetTextEdit>; + provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): ProviderResult<DocumentOnDropEdit>; } diff --git a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts index 41c53942b70..2ad4c28a6eb 100644 --- a/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts +++ b/src/vs/editor/contrib/copyPaste/browser/copyPasteController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener } from 'vs/base/browser/dom'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -15,26 +16,26 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { DocumentPasteEditProvider, SnippetTextEdit, WorkspaceEdit } from 'vs/editor/common/languages'; +import { DocumentPasteEdit, DocumentPasteEditProvider } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const vscodeClipboardMime = 'application/vnd.code.copyId'; const defaultPasteEditProvider = new class implements DocumentPasteEditProvider { - async provideDocumentPasteEdits(model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<WorkspaceEdit | undefined> { + pasteMimeTypes = [Mimes.text, 'text']; + + async provideDocumentPasteEdits(model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentPasteEdit | undefined> { const textDataTransfer = dataTransfer.get(Mimes.text) ?? dataTransfer.get('text'); if (textDataTransfer) { const text = await textDataTransfer.asString(); return { - edits: [{ - resource: model.uri, - edit: { range: selection, text }, - }] + insertText: text }; } @@ -76,8 +77,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi } const model = editor.getModel(); - const selection = this._editor.getSelection(); - if (!model || !selection) { + const selections = this._editor.getSelections(); + if (!model || !selections?.length) { return; } @@ -98,7 +99,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi const promise = createCancelablePromise(async token => { const results = await Promise.all(providers.map(provider => { - return provider.prepareDocumentPaste!(model, selection, dataTransfer, token); + return provider.prepareDocumentPaste!(model, selections, dataTransfer, token); })); for (const result of results) { @@ -115,8 +116,8 @@ export class CopyPasteController extends Disposable implements IEditorContributi })); this._register(addDisposableListener(container, 'paste', async (e: ClipboardEvent) => { - const selection = this._editor.getSelection(); - if (!e.clipboardData || !selection || !editor.hasModel()) { + const selections = this._editor.getSelections(); + if (!e.clipboardData || !selections?.length || !editor.hasModel()) { return; } @@ -125,21 +126,20 @@ export class CopyPasteController extends Disposable implements IEditorContributi return; } - const originalDocVersion = model.getVersionId(); - - const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model); - if (!providers.length) { + const handle = e.clipboardData?.getData(vscodeClipboardMime); + if (typeof handle !== 'string') { return; } - const handle = e.clipboardData?.getData(vscodeClipboardMime); - if (typeof handle !== 'string') { + const providers = this._languageFeaturesService.documentPasteEditProvider.ordered(model); + if (!providers.length) { return; } e.preventDefault(); e.stopImmediatePropagation(); + const originalDocVersion = model.getVersionId(); const tokenSource = new EditorStateCancellationTokenSource(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection); try { @@ -148,7 +148,7 @@ export class CopyPasteController extends Disposable implements IEditorContributi if (handle && this._currentClipboardItem?.handle === handle) { const toMergeDataTransfer = await this._currentClipboardItem.dataTransferPromise; toMergeDataTransfer.forEach((value, key) => { - dataTransfer.append(key, value); + dataTransfer.replace(key, value); }); } @@ -163,16 +163,25 @@ export class CopyPasteController extends Disposable implements IEditorContributi dataTransfer.delete(vscodeClipboardMime); for (const provider of [...providers, defaultPasteEditProvider]) { - const edit = await provider.provideDocumentPasteEdits(model, selection, dataTransfer, tokenSource.token); + if (!provider.pasteMimeTypes.some(type => { + if (type.toLowerCase() === DataTransfers.FILES.toLowerCase()) { + return [...dataTransfer.values()].some(item => item.asFile()); + } + return dataTransfer.has(type); + })) { + continue; + } + + const edit = await provider.provideDocumentPasteEdits(model, selections, dataTransfer, tokenSource.token); if (originalDocVersion !== model.getVersionId()) { return; } if (edit) { - if ((edit as WorkspaceEdit).edits) { - await this._bulkEditService.apply(ResourceEdit.convert(edit as WorkspaceEdit), { editor }); - } else { - performSnippetEdit(editor, edit as SnippetTextEdit); + performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, selections); + + if (edit.additionalEdit) { + await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor }); } return; } diff --git a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts index 48bac674986..8254c5faf12 100644 --- a/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts +++ b/src/vs/editor/contrib/dropIntoEditor/browser/dropIntoEditorContribution.ts @@ -13,14 +13,17 @@ import { URI } from 'vs/base/common/uri'; import { toVSDataTransfer } from 'vs/editor/browser/dnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IBulkEditService, ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection, SelectionDirection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { DocumentOnDropEditProvider, SnippetTextEdit } from 'vs/editor/common/languages'; +import { DocumentOnDropEdit, DocumentOnDropEditProvider } from 'vs/editor/common/languages'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { extractEditorsDropData } from 'vs/platform/dnd/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -37,6 +40,7 @@ export class DropIntoEditorController extends Disposable implements IEditorContr @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, ) { super(); @@ -87,7 +91,12 @@ export class DropIntoEditorController extends Disposable implements IEditorContr } if (edit) { - performSnippetEdit(editor, edit); + const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); + performSnippetEdit(editor, typeof edit.insertText === 'string' ? SnippetParser.escape(edit.insertText) : edit.insertText.snippet, [Selection.fromRange(range, SelectionDirection.LTR)]); + + if (edit.additionalEdit) { + await this._bulkEditService.apply(ResourceEdit.convert(edit.additionalEdit), { editor }); + } return; } } @@ -121,24 +130,26 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, ) { } - async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<SnippetTextEdit | undefined> { - const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - + async provideDocumentOnDropEdits(_model: ITextModel, _position: IPosition, dataTransfer: VSDataTransfer, _token: CancellationToken): Promise<DocumentOnDropEdit | undefined> { const urlListEntry = dataTransfer.get('text/uri-list'); if (urlListEntry) { const urlList = await urlListEntry.asString(); - return this.doUriListDrop(range, urlList); + const snippet = this.getUriListInsertText(urlList); + if (snippet) { + return { insertText: snippet }; + } } const textEntry = dataTransfer.get('text') ?? dataTransfer.get(Mimes.text); if (textEntry) { const text = await textEntry.asString(); - return { range, snippet: text }; + return { insertText: text }; } + return undefined; } - private doUriListDrop(range: Range, urlList: string): SnippetTextEdit | undefined { + private getUriListInsertText(urlList: string): string | undefined { const uris: URI[] = []; for (const resource of urlList.split('\n')) { try { @@ -152,7 +163,7 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { return; } - const snippet = uris + return uris .map(uri => { const root = this._workspaceContextService.getWorkspaceFolder(uri); if (root) { @@ -164,8 +175,6 @@ class DefaultOnDropProvider implements DocumentOnDropEditProvider { return uri.fsPath; }) .join(' '); - - return { range, snippet }; } } diff --git a/src/vs/editor/contrib/snippet/browser/snippetController2.ts b/src/vs/editor/contrib/snippet/browser/snippetController2.ts index f226b74458d..bb8282d9ee5 100644 --- a/src/vs/editor/contrib/snippet/browser/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/browser/snippetController2.ts @@ -9,9 +9,10 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorCommand, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { ISelection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { CompletionItem, CompletionItemKind, CompletionItemProvider, SnippetTextEdit } from 'vs/editor/common/languages'; +import { CompletionItem, CompletionItemKind, CompletionItemProvider } from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; @@ -335,13 +336,13 @@ registerEditorCommand(new CommandCtor({ // --- -export function performSnippetEdit(editor: ICodeEditor, edit: SnippetTextEdit) { +export function performSnippetEdit(editor: ICodeEditor, snippet: string, selections: ISelection[]): boolean { const controller = SnippetController2.get(editor); if (!controller) { return false; } editor.focus(); - editor.setSelection(edit.range); - controller.insert(edit.snippet); + editor.setSelections(selections ?? []); + controller.insert(snippet); return controller.isInSnippet(); } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 37a8579dcc5..19ce500b0f6 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6758,11 +6758,6 @@ declare namespace monaco.languages { eol?: editor.EndOfLineSequence; }; - export interface SnippetTextEdit { - range: IRange; - snippet: string; - } - /** * Interface used to format a model */ diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index e9231a1df85..db9d8a87c0e 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -9,7 +9,7 @@ import { RemoteAuthorities } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRemoteAuthorityResolverService, IRemoteConnectionData, ResolvedAuthority, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { getRemoteServerRootPath } from 'vs/platform/remote/common/remoteHosts'; +import { getRemoteServerRootPath, parseAuthorityWithOptionalPort } from 'vs/platform/remote/common/remoteHosts'; export class RemoteAuthorityResolverService extends Disposable implements IRemoteAuthorityResolverService { @@ -62,12 +62,9 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot private _doResolveAuthority(authority: string): ResolverResult { const connectionToken = this._connectionTokens.get(authority) || this._connectionToken; - if (authority.indexOf(':') >= 0) { - const pieces = authority.split(':'); - return { authority: { authority, host: pieces[0], port: parseInt(pieces[1], 10), connectionToken } }; - } - const port = (/^https:/.test(window.location.href) ? 443 : 80); - return { authority: { authority, host: authority, port: port, connectionToken } }; + const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80); + const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort); + return { authority: { authority, host: host, port: port, connectionToken } }; } _clearResolvedAuthority(authority: string): void { diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 760033e5789..783ea2e0896 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -33,3 +33,46 @@ export function getRemoteName(authority: string | undefined): string | undefined export function getRemoteServerRootPath(product: { quality?: string; commit?: string }): string { return `/${product.quality ?? 'oss'}-${product.commit ?? 'dev'}`; } + +export function parseAuthorityWithPort(authority: string): { host: string; port: number } { + const { host, port } = parseAuthority(authority); + if (typeof port === 'undefined') { + throw new Error(`Remote authority doesn't contain a port!`); + } + return { host, port }; +} + +export function parseAuthorityWithOptionalPort(authority: string, defaultPort: number): { host: string; port: number } { + let { host, port } = parseAuthority(authority); + if (typeof port === 'undefined') { + port = defaultPort; + } + return { host, port }; +} + +function parseAuthority(authority: string): { host: string; port: number | undefined } { + if (authority.indexOf('+') >= 0) { + throw new Error(`Remote authorities containing '+' need to be resolved!`); + } + + // check for ipv6 with port + const m1 = authority.match(/^(\[[0-9a-z:]+\]):(\d+)$/); + if (m1) { + return { host: m1[1], port: parseInt(m1[2], 10) }; + } + + // check for ipv6 without port + const m2 = authority.match(/^(\[[0-9a-z:]+\])$/); + if (m2) { + return { host: m2[1], port: undefined }; + } + + // anything with a trailing port + const m3 = authority.match(/(.*):(\d+)$/); + if (m3) { + return { host: m3[1], port: parseInt(m3[2], 10) }; + } + + // doesn't contain a port + return { host: authority, port: undefined }; +} diff --git a/src/vs/platform/remote/test/common/remoteHosts.test.ts b/src/vs/platform/remote/test/common/remoteHosts.test.ts new file mode 100644 index 00000000000..da7349774f6 --- /dev/null +++ b/src/vs/platform/remote/test/common/remoteHosts.test.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { parseAuthorityWithOptionalPort, parseAuthorityWithPort } from 'vs/platform/remote/common/remoteHosts'; + +suite('remoteHosts', () => { + + test('parseAuthority hostname', () => { + assert.deepStrictEqual(parseAuthorityWithPort('localhost:8080'), { host: 'localhost', port: 8080 }); + }); + + test('parseAuthority ipv4', () => { + assert.deepStrictEqual(parseAuthorityWithPort('127.0.0.1:8080'), { host: '127.0.0.1', port: 8080 }); + }); + + test('parseAuthority ipv6', () => { + assert.deepStrictEqual(parseAuthorityWithPort('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080'), { host: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', port: 8080 }); + }); + + test('parseAuthorityWithOptionalPort hostname', () => { + assert.deepStrictEqual(parseAuthorityWithOptionalPort('localhost:8080', 123), { host: 'localhost', port: 8080 }); + assert.deepStrictEqual(parseAuthorityWithOptionalPort('localhost', 123), { host: 'localhost', port: 123 }); + }); + + test('parseAuthorityWithOptionalPort ipv4', () => { + assert.deepStrictEqual(parseAuthorityWithOptionalPort('127.0.0.1:8080', 123), { host: '127.0.0.1', port: 8080 }); + assert.deepStrictEqual(parseAuthorityWithOptionalPort('127.0.0.1', 123), { host: '127.0.0.1', port: 123 }); + }); + + test('parseAuthorityWithOptionalPort ipv6', () => { + assert.deepStrictEqual(parseAuthorityWithOptionalPort('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8080', 123), { host: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', port: 8080 }); + assert.deepStrictEqual(parseAuthorityWithOptionalPort('[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', 123), { host: '[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', port: 123 }); + }); + +}); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 1545d21d0c5..1e4a6f043d3 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -30,7 +30,7 @@ import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy' import * as search from 'vs/workbench/contrib/search/common/search'; import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; -import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceEditDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape { @@ -368,21 +368,22 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread // --- copy paste action provider - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void { + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean, pasteMimeTypes: readonly string[]): void { const provider: languages.DocumentPasteEditProvider = { + pasteMimeTypes: pasteMimeTypes, + prepareDocumentPaste: supportsCopy - ? async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<VSDataTransfer | undefined> => { + ? async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken): Promise<VSDataTransfer | undefined> => { const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); if (token.isCancellationRequested) { return undefined; } - const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selection, dataTransferDto, token); + const result = await this._proxy.$prepareDocumentPaste(handle, model.uri, selections, dataTransferDto, token); if (!result) { return undefined; } - const dataTransferOut = new VSDataTransfer(); result.items.forEach(([type, item]) => { dataTransferOut.replace(type, createStringDataTransferItem(item.asString)); @@ -391,16 +392,17 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread } : undefined, - provideDocumentPasteEdits: async (model: ITextModel, selection: Selection, dataTransfer: VSDataTransfer, token: CancellationToken) => { + provideDocumentPasteEdits: async (model: ITextModel, selections: Selection[], dataTransfer: VSDataTransfer, token: CancellationToken) => { const d = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); - const result = await this._proxy.$providePasteEdits(handle, model.uri, selection, d, token); + const result = await this._proxy.$providePasteEdits(handle, model.uri, selections, d, token); if (!result) { - return; - } else if ((result as IWorkspaceEditDto).edits) { - return reviveWorkspaceEditDto(result as IWorkspaceEditDto); - } else { - return result as languages.SnippetTextEdit; + return undefined; } + + return { + insertText: result.insertText, + additionalEdit: result.additionalEdit ? reviveWorkspaceEditDto(result.additionalEdit) : undefined, + }; } }; @@ -933,11 +935,18 @@ class MainThreadDocumentOnDropEditProvider implements languages.DocumentOnDropEd private readonly _proxy: ExtHostLanguageFeaturesShape, ) { } - async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.SnippetTextEdit | null | undefined> { + async provideDocumentOnDropEdits(model: ITextModel, position: IPosition, dataTransfer: VSDataTransfer, token: CancellationToken): Promise<languages.DocumentOnDropEdit | null | undefined> { const request = this.dataTransfers.add(dataTransfer); try { const dataTransferDto = await typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); - return await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token); + const edit = await this._proxy.$provideDocumentOnDropEdits(this.handle, request.id, model.uri, position, dataTransferDto, token); + if (!edit) { + return undefined; + } + return { + insertText: edit.insertText, + additionalEdit: reviveWorkspaceEditDto(edit.additionalEdit), + }; } finally { request.dispose(); } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e649eb3a21a..cd752d09994 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -457,9 +457,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { return extHostLanguageFeatures.registerCodeActionProvider(extension, checkSelector(selector), provider, metadata); }, - registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + registerDocumentPasteEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { checkProposedApiEnabled(extension, 'documentPaste'); - return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider); + return extHostLanguageFeatures.registerDocumentPasteEditProvider(extension, checkSelector(selector), provider, metadata); }, registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); @@ -1267,7 +1267,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I SignatureHelpTriggerKind: extHostTypes.SignatureHelpTriggerKind, SignatureInformation: extHostTypes.SignatureInformation, SnippetString: extHostTypes.SnippetString, - SnippetTextEdit: extHostTypes.SnippetTextEdit, SourceBreakpoint: extHostTypes.SourceBreakpoint, StandardTokenType: extHostTypes.StandardTokenType, StatusBarAlignment: extHostTypes.StatusBarAlignment, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 865a0e5c4eb..60478d4a194 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -372,7 +372,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerLinkedEditingRangeProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string, supportsResolve: boolean): void; - $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean): void; + $registerPasteEditProvider(handle: number, selector: IDocumentFilterDto[], supportsCopy: boolean, pasteMimeTypes: readonly string[]): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; @@ -1713,6 +1713,16 @@ export interface IInlineValueContextDto { export type ITypeHierarchyItemDto = Dto<TypeHierarchyItem>; +export interface IPasteEditDto { + insertText: string | { snippet: string }; + additionalEdit?: IWorkspaceEditDto; +} + +export interface IDocumentOnDropEditDto { + insertText: string | { snippet: string }; + additionalEdit?: IWorkspaceEditDto; +} + export interface ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise<languages.DocumentSymbol[] | undefined>; $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise<ICodeLensListDto | undefined>; @@ -1731,8 +1741,8 @@ export interface ExtHostLanguageFeaturesShape { $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: languages.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>; $resolveCodeAction(handle: number, id: ChainedCacheId, token: CancellationToken): Promise<IWorkspaceEditDto | undefined>; $releaseCodeActions(handle: number, cacheId: number): void; - $prepareDocumentPaste(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise<DataTransferDTO | undefined>; - $providePasteEdits(handle: number, uri: UriComponents, range: IRange, dataTransfer: DataTransferDTO, token: CancellationToken): Promise<IWorkspaceEditDto | Dto<languages.SnippetTextEdit> | undefined>; + $prepareDocumentPaste(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise<DataTransferDTO | undefined>; + $providePasteEdits(handle: number, uri: UriComponents, ranges: IRange[], dataTransfer: DataTransferDTO, token: CancellationToken): Promise<IPasteEditDto | undefined>; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: languages.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; @@ -1771,7 +1781,7 @@ export interface ExtHostLanguageFeaturesShape { $provideTypeHierarchySupertypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>; $provideTypeHierarchySubtypes(handle: number, sessionId: string, itemId: string, token: CancellationToken): Promise<ITypeHierarchyItemDto[] | undefined>; $releaseTypeHierarchy(handle: number, sessionId: string): void; - $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined>; + $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: DataTransferDTO, token: CancellationToken): Promise<IDocumentOnDropEditDto | undefined>; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 040e4dffe64..7608468e560 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { mixin } from 'vs/base/common/objects'; import type * as vscode from 'vscode'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind, WorkspaceEdit } from 'vs/workbench/api/common/extHostTypes'; +import { Range, Disposable, CompletionList, SnippetString, CodeActionKind, SymbolInformation, DocumentSymbol, SemanticTokensEdits, SemanticTokens, SemanticTokensEdit, Location, InlineCompletionTriggerKindNew, InlineCompletionTriggerKind } from 'vs/workbench/api/common/extHostTypes'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import * as languages from 'vs/editor/common/languages'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; @@ -34,7 +34,6 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { isCancellationError, NotImplementedError } from 'vs/base/common/errors'; import { raceCancellationError } from 'vs/base/common/async'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { Dto } from 'vs/workbench/services/extensions/common/proxyIdentifier'; // --- adapter @@ -494,40 +493,39 @@ class DocumentPasteEditProvider { private readonly _handle: number, ) { } - async prepareDocumentPaste(resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> { + async prepareDocumentPaste(resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> { if (!this._provider.prepareDocumentPaste) { return undefined; } const doc = this._documents.getDocument(resource); - const vscodeRange = typeConvert.Range.to(range); + const vscodeRanges = ranges.map(range => typeConvert.Range.to(range)); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, () => { throw new NotImplementedError(); }); - await this._provider.prepareDocumentPaste(doc, vscodeRange, dataTransfer, token); + await this._provider.prepareDocumentPaste(doc, vscodeRanges, dataTransfer, token); return typeConvert.DataTransfer.toDataTransferDTO(dataTransfer); } - async providePasteEdits(requestId: number, resource: URI, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<undefined | extHostProtocol.IWorkspaceEditDto | Dto<languages.SnippetTextEdit>> { + async providePasteEdits(requestId: number, resource: URI, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<undefined | extHostProtocol.IPasteEditDto> { const doc = this._documents.getDocument(resource); - const vscodeRange = typeConvert.Range.to(range); + const vscodeRanges = ranges.map(range => typeConvert.Range.to(range)); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { return (await this._proxy.$resolveDocumentOnDropFileData(this._handle, requestId, index)).buffer; }); - const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRange, dataTransfer, token); + const edit = await this._provider.provideDocumentPasteEdits(doc, vscodeRanges, dataTransfer, token); if (!edit) { return; } - if (edit instanceof WorkspaceEdit) { - return typeConvert.WorkspaceEdit.from(edit); - } else { - return typeConvert.SnippetTextEdit.from(edit as vscode.SnippetTextEdit); - } + return { + insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, + additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined, + }; } } @@ -1799,7 +1797,7 @@ class DocumentOnDropEditAdapter { private readonly _handle: number, ) { } - async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> { + async provideDocumentOnDropEdits(requestId: number, uri: URI, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentOnDropEditDto | undefined> { const doc = this._documents.getDocument(uri); const pos = typeConvert.Position.to(position); const dataTransfer = typeConvert.DataTransfer.toDataTransfer(dataTransferDto, async (index) => { @@ -1810,7 +1808,10 @@ class DocumentOnDropEditAdapter { if (!edit) { return undefined; } - return typeConvert.SnippetTextEdit.from(edit); + return { + insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value }, + additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit) : undefined, + }; } } @@ -2454,26 +2455,26 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF return this._createDisposable(handle); } - $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<Dto<languages.SnippetTextEdit> | undefined> { + $provideDocumentOnDropEdits(handle: number, requestId: number, resource: UriComponents, position: IPosition, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IDocumentOnDropEditDto | undefined> { return this._withAdapter(handle, DocumentOnDropEditAdapter, adapter => Promise.resolve(adapter.provideDocumentOnDropEdits(requestId, URI.revive(resource), position, dataTransferDto, token)), undefined, undefined); } // --- copy/paste actions - registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider): vscode.Disposable { + registerDocumentPasteEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentPasteEditProvider, metadata: vscode.DocumentPasteProviderMetadata): vscode.Disposable { const handle = this._nextHandle(); this._adapter.set(handle, new AdapterData(new DocumentPasteEditProvider(this._proxy, this._documents, provider, handle), extension)); - this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste); + this._proxy.$registerPasteEditProvider(handle, this._transformDocumentSelector(selector), !!provider.prepareDocumentPaste, metadata.pasteMimeTypes); return this._createDisposable(handle); } - $prepareDocumentPaste(handle: number, resource: UriComponents, range: IRange, dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), range, dataTransfer, token), undefined, token); + $prepareDocumentPaste(handle: number, resource: UriComponents, ranges: IRange[], dataTransfer: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.DataTransferDTO | undefined> { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.prepareDocumentPaste(URI.revive(resource), ranges, dataTransfer, token), undefined, token); } - $providePasteEdits(handle: number, resource: UriComponents, range: IRange, dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IWorkspaceEditDto | Dto<languages.SnippetTextEdit> | undefined> { - return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), range, dataTransferDto, token), undefined, token); + $providePasteEdits(handle: number, resource: UriComponents, ranges: IRange[], dataTransferDto: extHostProtocol.DataTransferDTO, token: CancellationToken): Promise<extHostProtocol.IPasteEditDto | undefined> { + return this._withAdapter(handle, DocumentPasteEditProvider, adapter => adapter.providePasteEdits(0, URI.revive(resource), ranges, dataTransferDto, token), undefined, token); } // --- configuration diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index dcacff191cf..5bf0397e70b 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -562,15 +562,6 @@ export namespace TextEdit { } } -export namespace SnippetTextEdit { - export function from(edit: vscode.SnippetTextEdit): languages.SnippetTextEdit { - return { - range: Range.from(edit.range), - snippet: edit.snippet.value - }; - } -} - export namespace WorkspaceEdit { export interface IVersionInformationProvider { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 467bcc523c1..e3bdbf13e03 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -647,17 +647,6 @@ export class NotebookEdit implements vscode.NotebookEdit { } } -export class SnippetTextEdit implements vscode.SnippetTextEdit { - - range: vscode.Range; - snippet: vscode.SnippetString; - - constructor(range: Range, snippet: SnippetString) { - this.range = range; - this.snippet = snippet; - } -} - export interface IFileOperationOptions { overwrite?: boolean; ignoreIfExists?: boolean; diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index d8cea442776..6343fd24e4f 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -23,7 +23,7 @@ import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { ChangeEncodingAction, ChangeEOLAction, ChangeLanguageAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem, registerAction2 } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { @@ -244,8 +244,6 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(NewEditorGroupLeftAct registry.registerWorkbenchAction(SyncActionDescriptor.from(NewEditorGroupRightAction), 'View: New Editor Group to the Right', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(NewEditorGroupAboveAction), 'View: New Editor Group Above', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(NewEditorGroupBelowAction), 'View: New Editor Group Below', CATEGORIES.View.value); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateForwardAction, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Minus }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Minus } }), 'Go Forward'); -registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateBackwardsAction, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.Minus }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Minus } }), 'Go Back'); registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigatePreviousAction), 'Go Previous'); registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateForwardInEditsAction), 'Go Forward in Edit Locations'); registry.registerWorkbenchAction(SyncActionDescriptor.from(NavigateBackwardsInEditsAction), 'Go Back in Edit Locations'); @@ -273,6 +271,9 @@ registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousRe registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessLeastRecentlyUsedEditorInGroupAction, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }, ActiveEditorGroupEmptyContext.toNegated()), 'View: Quick Open Least Recently Used Editor in Group', CATEGORIES.View.value); registry.registerWorkbenchAction(SyncActionDescriptor.from(QuickAccessPreviousEditorFromHistoryAction), 'Quick Open Previous Editor from History'); +registerAction2(NavigateForwardAction); +registerAction2(NavigateBackwardsAction); + const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ id: quickAccessNavigateNextInEditorPickerId, @@ -314,9 +315,6 @@ if (isMacintosh) { }); } -MenuRegistry.appendMenuItem(MenuId.CommandCenter, { order: 1, command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); -MenuRegistry.appendMenuItem(MenuId.CommandCenter, { order: 2, command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); - // Empty Editor Group Toolbar MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: CLOSE_EDITOR_GROUP_COMMAND_ID, title: localize('closeGroupAction', "Close Group"), icon: Codicon.close }, group: 'navigation', order: 20 }); @@ -775,27 +773,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { // Main Menu Bar Contributions: -// Forward/Back -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '1_history_nav', - command: { - id: 'workbench.action.navigateBack', - title: localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), - precondition: ContextKeyExpr.has('canNavigateBack') - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '1_history_nav', - command: { - id: 'workbench.action.navigateForward', - title: localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), - precondition: ContextKeyExpr.has('canNavigateForward') - }, - order: 2 -}); - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { group: '1_history_nav', command: { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index a993829e847..bf39a77a241 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -26,6 +26,11 @@ import { Codicon } from 'vs/base/common/codicons'; import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; import { isLinux, isNative, isWindows } from 'vs/base/common/platform'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export class ExecuteCommandAction extends Action { @@ -1258,39 +1263,67 @@ export class OpenLastEditorInGroup extends AbstractNavigateEditorAction { } } -export class NavigateForwardAction extends Action { +export class NavigateForwardAction extends Action2 { static readonly ID = 'workbench.action.navigateForward'; static readonly LABEL = localize('navigateForward', "Go Forward"); - constructor( - id: string, - label: string, - @IHistoryService private readonly historyService: IHistoryService - ) { - super(id, label); + constructor() { + super({ + id: NavigateForwardAction.ID, + title: { value: localize('navigateForward', "Go Forward"), original: 'Go Forward', mnemonicTitle: localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward") }, + f1: true, + icon: Codicon.arrowRight, + precondition: ContextKeyExpr.has('canNavigateForward'), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + win: { primary: KeyMod.Alt | KeyCode.RightArrow }, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Minus }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Minus } + }, + menu: [ + { id: MenuId.MenubarGoMenu, group: '1_history_nav', order: 2 }, + { id: MenuId.CommandCenter, order: 2 } + ] + }); } - override async run(): Promise<void> { - await this.historyService.goForward(GoFilter.NONE); + async run(accessor: ServicesAccessor): Promise<void> { + const historyService = accessor.get(IHistoryService); + + await historyService.goForward(GoFilter.NONE); } } -export class NavigateBackwardsAction extends Action { +export class NavigateBackwardsAction extends Action2 { static readonly ID = 'workbench.action.navigateBack'; static readonly LABEL = localize('navigateBack', "Go Back"); - constructor( - id: string, - label: string, - @IHistoryService private readonly historyService: IHistoryService - ) { - super(id, label); + constructor() { + super({ + id: NavigateBackwardsAction.ID, + title: { value: localize('navigateBack', "Go Back"), original: 'Go Back', mnemonicTitle: localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back") }, + f1: true, + precondition: ContextKeyExpr.has('canNavigateBack'), + icon: Codicon.arrowLeft, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, + mac: { primary: KeyMod.WinCtrl | KeyCode.Minus }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Minus } + }, + menu: [ + { id: MenuId.MenubarGoMenu, group: '1_history_nav', order: 1 }, + { id: MenuId.CommandCenter, order: 1 } + ] + }); } - override async run(): Promise<void> { - await this.historyService.goBack(GoFilter.NONE); + async run(accessor: ServicesAccessor): Promise<void> { + const historyService = accessor.get(IHistoryService); + + await historyService.goBack(GoFilter.NONE); } } diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index b6e9f118b66..af98c5ae2c8 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -597,6 +597,7 @@ export class EditorDropTarget extends Themable { private registerListeners(): void { this._register(addDisposableListener(this.container, EventType.DRAG_ENTER, e => this.onDragEnter(e))); this._register(addDisposableListener(this.container, EventType.DRAG_LEAVE, () => this.onDragLeave())); + this._register(addDisposableListener(this.container, EventType.DRAG_OVER, e => this.onDragEnter(e))); [this.container, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => this.onDragEnd()))); } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 50c7f78b6b2..398674d57aa 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -690,8 +690,11 @@ export class DebugService implements IDebugService { const dataBreakpoints = this.model.getDataBreakpoints().filter(dbp => !dbp.canPersist); dataBreakpoints.forEach(dbp => this.model.removeDataBreakpoints(dbp.getId())); - if (this.viewsService.isViewVisible(REPL_VIEW_ID) && this.configurationService.getValue<IDebugConfiguration>('debug').console.closeOnEnd) { - this.viewsService.closeView(REPL_VIEW_ID); + if (this.configurationService.getValue<IDebugConfiguration>('debug').console.closeOnEnd) { + const debugConsoleContainer = this.viewDescriptorService.getViewContainerByViewId(REPL_VIEW_ID); + if (debugConsoleContainer && this.viewsService.isViewContainerVisible(debugConsoleContainer.id)) { + this.viewsService.closeViewContainer(debugConsoleContainer.id); + } } } })); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 01aac2fa0b6..d4e995fb9f1 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -96,10 +96,10 @@ export class VariablesView extends ViewPane { const viewState = this.savedViewState.get(stackFrame.getId()); await this.tree.setInput(stackFrame, viewState); - // Automatically expand the first scope if it is not expensive and if all scopes are collapsed + // Automatically expand the first non-expensive scope const scopes = await stackFrame.getScopes(); const toExpand = scopes.find(s => !s.expensive); - if (toExpand && (scopes.every(s => this.tree.isCollapsed(s)) || !this.autoExpandedScopes.has(toExpand.getId()))) { + if (toExpand) { this.autoExpandedScopes.add(toExpand.getId()); await this.tree.expand(toExpand); } diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index c05fc04c956..4b20e069cd2 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -7,6 +7,7 @@ import { distinct, lastIndex } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { decodeBase64, encodeBase64, VSBuffer } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { stringHash } from 'vs/base/common/hash'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { mixin } from 'vs/base/common/objects'; @@ -351,7 +352,7 @@ export class Scope extends ExpressionContainer implements IScope { constructor( stackFrame: IStackFrame, - index: number, + id: number, public readonly name: string, reference: number, public expensive: boolean, @@ -359,7 +360,7 @@ export class Scope extends ExpressionContainer implements IScope { indexedVariables?: number, public readonly range?: IRange ) { - super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${index}`, namedVariables, indexedVariables); + super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${id}`, namedVariables, indexedVariables); } override toString(): string { @@ -417,12 +418,17 @@ export class StackFrame implements IStackFrame { return []; } - const scopeNameIndexes = new Map<string, number>(); + const usedIds = new Set<number>(); return response.body.scopes.map(rs => { - const previousIndex = scopeNameIndexes.get(rs.name); - const index = typeof previousIndex === 'number' ? previousIndex + 1 : 0; - scopeNameIndexes.set(rs.name, index); - return new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, + // form the id based on the name and location so that it's the + // same across multiple pauses to retain expansion state + let id = 0; + do { + id = stringHash(`${rs.name}:${rs.line}:${rs.column}`, id); + } while (usedIds.has(id)); + + usedIds.add(id); + return new Scope(this, id, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined); }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 48bf5fb81e3..8fa3b9b8f93 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -16,13 +16,13 @@ import { append, $, finalHandler, join, addDisposableListener, EventType, setPar import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; +import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IExtensionManifest, IKeyBinding, IView, IViewContainer } from 'vs/platform/extensions/common/extensions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions'; -import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, ExtensionWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState, IExtensionContainer } from 'vs/workbench/contrib/extensions/common/extensions'; +import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, ExtensionWidget, ExtensionStatusWidget, ExtensionRecommendationWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { @@ -66,8 +66,7 @@ import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { errorIcon, infoIcon, preReleaseIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { errorIcon, infoIcon, preReleaseIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -147,8 +146,6 @@ interface IExtensionEditorTemplate { description: HTMLElement; actionsAndStatusContainer: HTMLElement; extensionActionBar: ActionBar; - status: HTMLElement; - recommendation: HTMLElement; navbar: NavBar; content: HTMLElement; header: HTMLElement; @@ -251,7 +248,6 @@ export class ExtensionEditor extends EditorPane { @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, @IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService, - @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, @IStorageService storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IWebviewService private readonly webviewService: IWebviewService, @@ -310,7 +306,7 @@ export class ExtensionEditor extends EditorPane { rating.setAttribute('role', 'link'); // #132645 const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, rating, false); - const widgets = [ + const widgets: ExtensionWidget[] = [ remoteBadge, versionWidget, preReleaseWidget, @@ -375,14 +371,30 @@ export class ExtensionEditor extends EditorPane { extensionActionBar.setFocusable(true); })); - const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); - for (const disposable of [...actions, ...widgets, extensionContainers]) { + const otherExtensionContainers: IExtensionContainer[] = []; + const extensionStatusAction = this.instantiationService.createInstance(ExtensionStatusAction); + const extensionStatusWidget = this._register(this.instantiationService.createInstance(ExtensionStatusWidget, append(actionsAndStatusContainer, $('.status')), extensionStatusAction)); + + otherExtensionContainers.push(extensionStatusAction, new class extends ExtensionWidget { + render() { + actionsAndStatusContainer.classList.toggle('list-layout', this.extension?.state === ExtensionState.Installed); + } + }()); + + const recommendationWidget = this.instantiationService.createInstance(ExtensionRecommendationWidget, append(details, $('.recommendation'))); + widgets.push(recommendationWidget); + + this._register(Event.any(extensionStatusWidget.onDidRender, recommendationWidget.onDidRender)(() => { + if (this.dimension) { + this.layout(this.dimension); + } + })); + + const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, ...otherExtensionContainers]); + for (const disposable of [...actions, ...widgets, ...otherExtensionContainers, extensionContainers]) { this._register(disposable); } - const status = append(actionsAndStatusContainer, $('.status')); - const recommendation = append(details, $('.recommendation')); - this._register(Event.chain(extensionActionBar.onDidRun) .map(({ error }) => error) .filter(error => !!error) @@ -411,8 +423,6 @@ export class ExtensionEditor extends EditorPane { rating, actionsAndStatusContainer, extensionActionBar, - status, - recommendation, set extension(extension: IExtension) { extensionContainers.extension = extension; }, @@ -544,9 +554,6 @@ export class ExtensionEditor extends EditorPane { })); } - this.setStatus(extension, template); - this.setRecommendationText(extension, template); - const manifest = await this.extensionManifest.get().promise; if (token.isCancellationRequested) { return; @@ -620,72 +627,6 @@ export class ExtensionEditor extends EditorPane { template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables); } - private setStatus(extension: IExtension, template: IExtensionEditorTemplate): void { - const disposables = this.transientDisposables.add(new DisposableStore()); - const extensionStatus = disposables.add(this.instantiationService.createInstance(ExtensionStatusAction)); - extensionStatus.extension = extension; - const updateStatus = (layout: boolean) => { - disposables.clear(); - reset(template.status); - const status = extensionStatus.status; - if (status) { - if (status.icon) { - const statusIconActionBar = disposables.add(new ActionBar(template.status, { animated: false })); - statusIconActionBar.push(extensionStatus, { icon: true, label: false }); - } - disposables.add(this.renderMarkdownText(status.message.value, append(template.status, $('.status-text')))); - } - if (layout && this.dimension) { - this.layout(this.dimension); - } - }; - updateStatus(false); - this.transientDisposables.add(extensionStatus.onDidChangeStatus(() => updateStatus(true))); - - const updateActionLayout = () => template.actionsAndStatusContainer.classList.toggle('list-layout', extension.state === ExtensionState.Installed); - updateActionLayout(); - this.transientDisposables.add(this.extensionsWorkbenchService.onChange(() => updateActionLayout())); - } - - private setRecommendationText(extension: IExtension, template: IExtensionEditorTemplate): void { - const updateRecommendationText = (layout: boolean) => { - reset(template.recommendation); - const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); - if (extRecommendations[extension.identifier.id.toLowerCase()]) { - const reasonText = extRecommendations[extension.identifier.id.toLowerCase()].reasonText; - if (reasonText) { - append(template.recommendation, $(`div${ThemeIcon.asCSSSelector(starEmptyIcon)}`)); - append(template.recommendation, $(`div.recommendation-text`, undefined, reasonText)); - } - } else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(extension.identifier.id.toLowerCase()) !== -1) { - append(template.recommendation, $(`div.recommendation-text`, undefined, localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension."))); - } - if (layout && this.dimension) { - this.layout(this.dimension); - } - }; - if (extension.deprecationInfo || extension.state === ExtensionState.Installed) { - reset(template.recommendation); - return; - } - updateRecommendationText(false); - this.transientDisposables.add(this.extensionRecommendationsService.onDidChangeRecommendations(() => updateRecommendationText(true))); - } - - private renderMarkdownText(markdownText: string, parent: HTMLElement): IDisposable { - const disposables = new DisposableStore(); - const rendered = disposables.add(renderMarkdown(new MarkdownString(markdownText, { isTrusted: true, supportThemeIcons: true }), { - actionHandler: { - callback: (content) => { - this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); - }, - disposables: disposables - } - })); - append(parent, rendered.element); - return disposables; - } - override clearInput(): void { this.contentDisposables.clear(); this.transientDisposables.clear(); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index b339a649e84..cba60a0ef88 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -2944,6 +2944,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const errorColor = theme.getColor(editorErrorForeground); if (errorColor) { collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.extension-status-error { color: ${errorColor}; }`); + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(errorIcon)} { color: ${errorColor}; }`); } @@ -2951,6 +2952,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const warningColor = theme.getColor(editorWarningForeground); if (warningColor) { collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.extension-status-warning { color: ${warningColor}; }`); + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(warningIcon)} { color: ${warningColor}; }`); } @@ -2958,6 +2960,7 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = const infoColor = theme.getColor(editorInfoForeground); if (infoColor) { collector.addRule(`.monaco-action-bar .action-item .action-label.extension-action.extension-status-info { color: ${infoColor}; }`); + collector.addRule(`.extension-editor .header .actions-status-container > .status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); collector.addRule(`.extension-editor .body .subcontent .runtime-status ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); collector.addRule(`.monaco-hover.extension-hover .markdown-hover .hover-contents ${ThemeIcon.asCSSSelector(infoIcon)} { color: ${infoColor}; }`); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index 1760db32044..ef58205dfb2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -7,16 +7,16 @@ import 'vs/css!./media/extensionsWidgets'; import * as semver from 'vs/base/common/semver/semver'; import { Disposable, toDisposable, DisposableStore, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions'; -import { append, $ } from 'vs/base/browser/dom'; +import { append, $, reset } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { EnablementState, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; +import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { ILabelService } from 'vs/platform/label/common/label'; import { extensionButtonProminentBackground, extensionButtonProminentForeground, ExtensionStatusAction, ReloadAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IThemeService, ThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; -import { Event } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -32,6 +32,9 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import Severity from 'vs/base/common/severity'; import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; import { Color } from 'vs/base/common/color'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { onUnexpectedError } from 'vs/base/common/errors'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension | null = null; @@ -576,6 +579,93 @@ export class ExtensionHoverWidget extends ExtensionWidget { } +export class ExtensionStatusWidget extends ExtensionWidget { + + private readonly renderDisposables = this._register(new DisposableStore()); + + private readonly _onDidRender = this._register(new Emitter<void>()); + readonly onDidRender: Event<void> = this._onDidRender.event; + + constructor( + private readonly container: HTMLElement, + private readonly extensionStatusAction: ExtensionStatusAction, + @IOpenerService private readonly openerService: IOpenerService, + ) { + super(); + this.render(); + this._register(extensionStatusAction.onDidChangeStatus(() => this.render())); + } + + render(): void { + reset(this.container); + const extensionStatus = this.extensionStatusAction.status; + if (extensionStatus) { + const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true }); + if (extensionStatus.icon) { + markdown.appendMarkdown(`$(${extensionStatus.icon.id}) `); + } + markdown.appendMarkdown(extensionStatus.message.value); + const rendered = this.renderDisposables.add(renderMarkdown(markdown, { + actionHandler: { + callback: (content) => { + this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); + }, + disposables: this.renderDisposables + } + })); + append(this.container, rendered.element); + } + this._onDidRender.fire(); + } +} + +export class ExtensionRecommendationWidget extends ExtensionWidget { + + private readonly _onDidRender = this._register(new Emitter<void>()); + readonly onDidRender: Event<void> = this._onDidRender.event; + + constructor( + private readonly container: HTMLElement, + @IExtensionRecommendationsService private readonly extensionRecommendationsService: IExtensionRecommendationsService, + @IExtensionIgnoredRecommendationsService private readonly extensionIgnoredRecommendationsService: IExtensionIgnoredRecommendationsService, + ) { + super(); + this.render(); + this._register(this.extensionRecommendationsService.onDidChangeRecommendations(() => this.render())); + } + + render(): void { + reset(this.container); + const recommendationStatus = this.getRecommendationStatus(); + if (recommendationStatus) { + if (recommendationStatus.icon) { + append(this.container, $(`div${ThemeIcon.asCSSSelector(recommendationStatus.icon)}`)); + } + append(this.container, $(`div.recommendation-text`, undefined, recommendationStatus.message)); + } + this._onDidRender.fire(); + } + + private getRecommendationStatus(): { icon: ThemeIcon | undefined; message: string } | undefined { + if (!this.extension + || this.extension.deprecationInfo + || this.extension.state === ExtensionState.Installed + ) { + return undefined; + } + const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); + if (extRecommendations[this.extension.identifier.id.toLowerCase()]) { + const reasonText = extRecommendations[this.extension.identifier.id.toLowerCase()].reasonText; + if (reasonText) { + return { icon: starEmptyIcon, message: reasonText }; + } + } else if (this.extensionIgnoredRecommendationsService.globalIgnoredRecommendations.indexOf(this.extension.identifier.id.toLowerCase()) !== -1) { + return { icon: undefined, message: localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.") }; + } + return undefined; + } +} + // Rating icon export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hcDark: '#FF8E00', hcLight: textLinkForeground }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true); export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', { dark: textLinkForeground, light: textLinkForeground, hcDark: textLinkForeground, hcLight: textLinkForeground }, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 3c9a5a0e1b5..49d44c6e405 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -249,49 +249,36 @@ .extension-editor > .header > .details > .actions-status-container > .status { line-height: 22px; font-size: 90%; - display: flex; + margin-top: 3px; } .extension-editor > .header > .details > .actions-status-container.list-layout > .status { margin-top: 5px; } -.extension-editor > .header > .details > .actions-status-container > .status > .monaco-action-bar { - height: 22px; - margin-right: 2px; -} - -.extension-editor > .header > .details > .actions-status-container > .status > .monaco-action-bar .extension-action { - margin-top: 3px; -} - -.extension-editor > .header > .details > .actions-status-container.list-layout > .status > .monaco-action-bar .extension-action { - margin-top: 0px; -} - -.extension-editor > .header > .details > .actions-status-container:not(.list-layout) > .status > .status-text { - margin-top: 2px; +.extension-editor > .header > .details > .actions-status-container > .status .codicon { + vertical-align: text-bottom; } .extension-editor > .header > .details > .pre-release-text p, -.extension-editor > .header > .details > .actions-status-container > .status > .status-text p { +.extension-editor > .header > .details > .actions-status-container > .status p { margin-top: 0px; margin-bottom: 0px; } .extension-editor > .header > .details > .pre-release-text a, -.extension-editor > .header > .details > .actions-status-container > .status > .status-text a { +.extension-editor > .header > .details > .actions-status-container > .status a { color: var(--vscode-textLink-foreground) } .extension-editor > .header > .details > .pre-release-text a:hover, -.extension-editor > .header > .details > .actions-status-container > .status > .status-text a:hover { +.extension-editor > .header > .details > .actions-status-container > .status a:hover { text-decoration: underline; color: var(--vscode-textLink-activeForeground) } .extension-editor > .header > .details > .pre-release-text a:active, -.extension-editor > .header > .details > .actions-status-container > .status > .status-text a:active { +.extension-editor > .header > .details > .actions-status-container > .status a:active { color: var(--vscode-textLink-activeForeground) } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index ec28a88e81f..eddd826fa33 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -9,7 +9,7 @@ import { IPager } from 'vs/base/common/paging'; import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; @@ -136,7 +136,7 @@ export interface IExtensionsConfiguration { closeExtensionDetailsOnViewChange: boolean; } -export interface IExtensionContainer { +export interface IExtensionContainer extends IDisposable { extension: IExtension | null; updateWhenCounterExtensionChanges?: boolean; update(): void; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index 062c0d23e29..41f221e0db0 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -65,10 +65,10 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { return this._onDidChange.event; } - provideDecorations(resource: URI): IDecorationData | undefined { + async provideDecorations(resource: URI): Promise<IDecorationData | undefined> { const fileStat = this.explorerService.findClosest(resource); if (!fileStat) { - return undefined; + throw new Error('ExplorerItem not found'); } return provideDecorations(fileStat); diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts index ea5f4383b6e..c59d84821b2 100644 --- a/src/vs/workbench/contrib/localization/browser/localeService.ts +++ b/src/vs/workbench/contrib/localization/browser/localeService.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { language } from 'vs/base/common/platform'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; @@ -32,5 +31,3 @@ export class WebLocaleService implements ILocaleService { return true; } } - -registerSingleton(ILocaleService, WebLocaleService, true); diff --git a/src/vs/workbench/contrib/localization/browser/localization.contribution.ts b/src/vs/workbench/contrib/localization/browser/localization.contribution.ts index 79f2f0a777b..85716e8bcb9 100644 --- a/src/vs/workbench/contrib/localization/browser/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/browser/localization.contribution.ts @@ -4,7 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { WebLocaleService } from 'vs/workbench/contrib/localization/browser/localeService'; import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; +import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; + +registerSingleton(ILocaleService, WebLocaleService, true); // Register action to configure locale and related settings registerAction2(ConfigureDisplayLanguageAction); diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts index 5d51fb89d0f..5bdb7500724 100644 --- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -45,7 +45,7 @@ export class ConfigureDisplayLanguageAction extends Action2 { qp.placeholder = localize('chooseLocale', "Select Display Language"); if (installedLanguages?.length) { - const items: Array<ILanguagePackItem | IQuickPickSeparator> = [{ type: 'separator', label: localize('installed', "Installed languages") }]; + const items: Array<ILanguagePackItem | IQuickPickSeparator> = [{ type: 'separator', label: localize('installed', "Installed") }]; qp.items = items.concat(installedLanguages); } @@ -62,7 +62,7 @@ export class ConfigureDisplayLanguageAction extends Action2 { if (newLanguages.length) { qp.items = [ ...qp.items, - { type: 'separator', label: localize('available', "Available languages") }, + { type: 'separator', label: localize('available', "Available") }, ...newLanguages ]; } diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts index 7f1a40e718c..86bc2246730 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts @@ -5,7 +5,6 @@ import { language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; @@ -14,6 +13,8 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { localize } from 'vs/nls'; export class NativeLocaleService implements ILocaleService { _serviceBrand: undefined; @@ -25,6 +26,7 @@ export class NativeLocaleService implements ILocaleService { @ILanguagePackService private readonly languagePackService: ILanguagePackService, @IPaneCompositePartService private readonly paneCompositePartService: IPaneCompositePartService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IProgressService private readonly progressService: IProgressService ) { } private async writeLocaleValue(locale: string | undefined): Promise<void> { @@ -42,16 +44,26 @@ export class NativeLocaleService implements ILocaleService { // Only Desktop has the concept of installing language packs so we only do this for Desktop // and only if the language pack is not installed if (!installedLanguages.some(installedLanguage => installedLanguage.id === languagePackItem.id)) { - // Show the view so the user can see the language pack to be installed - const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar); - (viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`); // Only actually install a language pack from Microsoft if (languagePackItem.galleryExtension?.publisher.toLowerCase() !== 'ms-ceintl') { + // Show the view so the user can see the language pack that they should install + // as of now, there are no 3rd party language packs available on the Marketplace. + const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar); + (viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`); return false; } - await this.extensionManagementService.installFromGallery(languagePackItem.galleryExtension); + await this.progressService.withProgress( + { + location: ProgressLocation.Notification, + title: localize('installing', "Installing {0} language support...", languagePackItem.label), + }, + progress => this.extensionManagementService.installFromGallery(languagePackItem.galleryExtension!, { + // Setting this to false is how you get the extension to be synced with Settings Sync (if enabled). + isMachineScoped: false, + }) + ); } await this.writeLocaleValue(locale); @@ -75,5 +87,3 @@ export class NativeLocaleService implements ILocaleService { } } } - -registerSingleton(ILocaleService, NativeLocaleService, true); diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts index b04105ab3c9..29726f689d2 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localization.contribution.ts @@ -25,6 +25,11 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { ViewContainerLocation } from 'vs/workbench/common/views'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ClearDisplayLanguageAction, ConfigureDisplayLanguageAction } from 'vs/workbench/contrib/localization/browser/localizationsActions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { NativeLocaleService } from 'vs/workbench/contrib/localization/electron-sandbox/localeService'; + +registerSingleton(ILocaleService, NativeLocaleService, true); // Register action to configure locale and related settings registerAction2(ConfigureDisplayLanguageAction); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts index b850a842e5d..b01b1e9c46a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/editorGutter.ts @@ -11,7 +11,7 @@ import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model'; export class EditorGutter< T extends IGutterItemInfo = IGutterItemInfo - > extends Disposable { +> extends Disposable { private readonly scrollTop = observableFromEvent( this._editor.onDidScrollChange, (e) => this._editor.getScrollTop() diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 8fbc0999419..712c2369044 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -285,15 +285,15 @@ export class NotebookClipboardContribution extends Disposable { } if (PasteAction) { - PasteAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { + this._register(PasteAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { return this.runPasteAction(accessor); - }); + })); } if (CutAction) { - CutAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { + this._register(CutAction.addImplementation(PRIORITY, 'notebook-clipboard', accessor => { return this.runCutAction(accessor); - }); + })); } } diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index b48922916a0..cdb8be3b643 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -131,21 +131,20 @@ const notebookRendererContribution: IJSONSchema = { { type: 'string', }, - // todo@connor4312 + @mjbvz: uncomment this once it's ready for external adoption - // { - // type: 'object', - // required: ['extends', 'path'], - // properties: { - // extends: { - // type: 'string', - // description: nls.localize('contributes.notebook.renderer.entrypoint.extends', 'Existing renderer that this one extends.'), - // }, - // path: { - // type: 'string', - // description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'), - // }, - // } - // } + { + type: 'object', + required: ['extends', 'path'], + properties: { + extends: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.entrypoint.extends', 'Existing renderer that this one extends.'), + }, + path: { + type: 'string', + description: nls.localize('contributes.notebook.renderer.entrypoint', 'File to load in the webview to render the extension.'), + }, + } + } ] }, [NotebookRendererContribution.hardDependencies]: { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index e678891f59a..9d87162b033 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -75,7 +75,7 @@ export const RENDERER_EQUIVALENT_EXTENSIONS: ReadonlyMap<string, ReadonlySet<str export const RENDERER_NOT_AVAILABLE = '_notAvailable'; -export type NotebookRendererEntrypoint = string | { extends: string; path: string }; +export type NotebookRendererEntrypoint = string | { readonly extends: string; readonly path: string }; export enum NotebookRunState { Running = 1, diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 7b4684b2961..20f1af21202 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -109,15 +109,23 @@ /* padding must be on action-label because it has the bottom-border, because that's where the .checked class is */ } +.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item.focused { + outline-offset: -1.5px; +} + .settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item .action-label { text-transform: none; font-size: 13px; - padding-bottom: 7px; + padding-bottom: 6.5px; padding-top: 7px; padding-left: 8px; padding-right: 8px; } +.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item .action-label .dropdown-icon { + padding-top: 2px; +} + .settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget > .monaco-action-bar .action-item .action-label:not(.checked):not(:focus) { /* Add an extra pixel due to it not getting the outline */ padding-bottom: 8px; diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index 80d5d19302d..c8f4524d017 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -37,7 +37,7 @@ export function isSCMResource(element: any): element is ISCMResource { return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup); } -const compareActions = (a: IAction, b: IAction) => a.id === b.id; +const compareActions = (a: IAction, b: IAction) => a.id === b.id && a.enabled === b.enabled; export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, primaryGroup?: string): IDisposable { let cachedDisposable: IDisposable = Disposable.None; diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 4a77f401057..41c02ea5f9b 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1480,7 +1480,7 @@ namespace ConfiguringTask { for (const required of typeDeclaration.required) { const value = result.configures[required]; if (value) { - label = label + ' ' + value; + label = label + ': ' + value; break; } } diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index 46fe4c4813b..b40e0e33562 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -71,6 +71,14 @@ __vsc_command_complete() { __vsc_update_cwd } +if [[ -o NOUNSET ]]; then + if [ -z "${RPROMPT-}" ]; then + RPROMPT="" + fi + if [ -z "${PREFIX-}" ]; then + PREFIX="" + fi +fi __vsc_update_prompt() { __vsc_prior_prompt="$PS1" __vsc_in_command_execution="" diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 34cdd7e2d3e..b8fd8ff6a1f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -929,10 +929,12 @@ export class TerminalService implements ITerminalService { // Await the initialization of available profiles as long as this is not a pty terminal or a // local terminal in a remote workspace as profile won't be used in those cases and these // terminals need to be launched before remote connections are established. - const isPtyTerminal = options?.config && 'customPtyImplementation' in options.config; - const isLocalInRemoteTerminal = this._remoteAgentService.getConnection() && URI.isUri(options?.cwd) && options?.cwd.scheme === Schemas.vscodeFileResource; - if (!isPtyTerminal && !isLocalInRemoteTerminal) { - await this._terminalProfileService.profilesReady; + if (this._terminalProfileService.availableProfiles.length === 0) { + const isPtyTerminal = options?.config && 'customPtyImplementation' in options.config; + const isLocalInRemoteTerminal = this._remoteAgentService.getConnection() && URI.isUri(options?.cwd) && options?.cwd.scheme === Schemas.vscodeFileResource; + if (!isPtyTerminal && !isLocalInRemoteTerminal) { + await this._terminalProfileService.profilesReady; + } } const config = options?.config || this._terminalProfileService.availableProfiles?.find(p => p.profileName === this._terminalProfileService.getDefaultProfileName()); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 4caf924b497..cc3510f39dc 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -118,8 +118,11 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } public clearDecorations(): void { - this._placeholderDecoration?.dispose(); - this._placeholderDecoration?.marker.dispose(); + if (this._placeholderDecoration) { + this._placeholderDecoration.marker.dispose(); + this._placeholderDecoration.dispose(); + this._placeholderDecoration = undefined; + } for (const value of this._decorations.values()) { value.decoration.dispose(); dispose(value.disposables); diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index 2aa2aa38445..42a7850a675 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -398,7 +398,8 @@ export class DecorationsService implements IDecorationsService { private _keepItem(map: DecorationEntry, provider: IDecorationsProvider, uri: URI, data: IDecorationData | undefined): IDecorationData | null { const deco = data ? data : null; - const old = map.set(provider, deco); + const old = map.get(provider); + map.set(provider, deco); if (deco || old) { // only fire event when something changed this._onDidChangeDecorationsDelayed.fire(uri); diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index ea87ef0642a..7b25b8f9142 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -30,7 +30,7 @@ import { flatten } from 'vs/base/common/arrays'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { getRemoteName, parseAuthorityWithPort } from 'vs/platform/remote/common/remoteHosts'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IWebWorkerExtensionHostDataProvider, IWebWorkerExtensionHostInitData, WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -349,12 +349,12 @@ export abstract class ElectronExtensionService extends AbstractExtensionService const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { // This authority does not need to be resolved, simply parse the port number - const lastColon = remoteAuthority.lastIndexOf(':'); + const { host, port } = parseAuthorityWithPort(remoteAuthority); return { authority: { authority: remoteAuthority, - host: remoteAuthority.substring(0, lastColon), - port: parseInt(remoteAuthority.substring(lastColon + 1), 10), + host, + port, connectionToken: undefined } }; diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index 99705ed5b01..dd9ddd715b1 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -83,7 +83,6 @@ import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; import 'vs/workbench/services/search/electron-sandbox/searchService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; -import 'vs/workbench/contrib/localization/electron-sandbox/localeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 65562cad3ab..f0833043d64 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -62,7 +62,6 @@ import 'vs/workbench/services/files/browser/elevatedFileService'; import 'vs/workbench/services/workingCopy/browser/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import 'vs/workbench/contrib/localization/browser/localeService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts index 1777c8d4145..29335e1df21 100644 --- a/src/vscode-dts/vscode.proposed.documentPaste.d.ts +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -19,11 +19,11 @@ declare module 'vscode' { * a {@link DataTransfer} and is passed back to the provider in {@link provideDocumentPasteEdits}. * * @param document Document where the copy took place. - * @param range Range being copied in the `document`. + * @param ranges Ranges being copied in the `document`. * @param dataTransfer The data transfer associated with the copy. You can store additional values on this for later use in {@link provideDocumentPasteEdits}. * @param token A cancellation token. */ - prepareDocumentPaste?(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): void | Thenable<void>; + prepareDocumentPaste?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): void | Thenable<void>; /** * Invoked before the user pastes into a document. @@ -31,16 +31,40 @@ declare module 'vscode' { * In this method, extensions can return a workspace edit that replaces the standard pasting behavior. * * @param document Document being pasted into - * @param range Currently selected range in the document. + * @param ranges Currently selected ranges in the document. * @param dataTransfer The data transfer associated with the paste. * @param token A cancellation token. * * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. */ - provideDocumentPasteEdits(document: TextDocument, range: Range, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<WorkspaceEdit | SnippetTextEdit>; + provideDocumentPasteEdits(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<DocumentPasteEdit>; + } + + /** + * An operation applied on paste + */ + interface DocumentPasteEdit { + /** + * The text or snippet to insert at the pasted locations. + */ + readonly insertText: string | SnippetString; + + /** + * An optional additional edit to apply on paste. + */ + readonly additionalEdit?: WorkspaceEdit; + } + + interface DocumentPasteProviderMetadata { + /** + * Mime types that `provideDocumentPasteEdits` should be invoked for. + * + * Use the special `files` mimetype to indicate the provider should be invoked if any files are present in the `DataTransfer`. + */ + readonly pasteMimeTypes: readonly string[]; } namespace languages { - export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider): Disposable; + export function registerDocumentPasteEditProvider(selector: DocumentSelector, provider: DocumentPasteEditProvider, metadata: DocumentPasteProviderMetadata): Disposable; } } diff --git a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts index 5468bc218cd..1b0d76db88d 100644 --- a/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts +++ b/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts @@ -7,12 +7,6 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/142990 - export class SnippetTextEdit { - snippet: SnippetString; - range: Range; - constructor(range: Range, snippet: SnippetString); - } - /** * Provider which handles dropping of resources into a text editor. * @@ -27,10 +21,25 @@ declare module 'vscode' { * @param dataTransfer A {@link DataTransfer} object that holds data about what is being dragged and dropped. * @param token A cancellation token. * - * @return A {@link SnippetTextEdit} or a thenable that resolves to such. The lack of a result can be + * @return A {@link DocumentDropEdit} or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined` or `null`. */ - provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<SnippetTextEdit>; + provideDocumentOnDropEdits(document: TextDocument, position: Position, dataTransfer: DataTransfer, token: CancellationToken): ProviderResult<DocumentDropEdit>; + } + + /** + * An edit operation applied on drop. + */ + export interface DocumentDropEdit { + /** + * The text or snippet to insert at the drop location. + */ + readonly insertText: string | SnippetString; + + /** + * An optional additional edit to apply on drop. + */ + readonly additionalEdit?: WorkspaceEdit; } export namespace languages { diff --git a/yarn.lock b/yarn.lock index 3e537569240..83a087110e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11390,10 +11390,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.8.0-dev.20220518: - version "4.8.0-dev.20220518" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220518.tgz#3082c89c764daece904480552b9f2c3f5ce79c56" - integrity sha512-yczRLiowXD4THxpe2DrClYXsmIRt9VPDft1dat4Le50mQwuUcmvdqD43o4hmlbNP7HpyeEYX51KXozGq1s7zlw== +typescript@^4.8.0-dev.20220608: + version "4.8.0-dev.20220608" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220608.tgz#6da2ea3c79307b1dfc1cb3692b142cc13f974a98" + integrity sha512-cayjhBae7omJ5MYxt4fO2PNW7kjYNCRqOOQbafxuzP0SWpqznzPROuNQqiBK1KJfgnRxBWeyqGBP52mdZDYZVw== typical@^4.0.0: version "4.0.0" |