Merge remote-tracking branch 'origin/master' into pr/baileyherbert/90631

This commit is contained in:
Alex Dima
2020-02-17 16:45:37 +01:00
150 changed files with 4176 additions and 1629 deletions
+1 -1
View File
@@ -46,7 +46,7 @@
},
{
"name": "ms-vscode.js-debug-nightly",
"version": "2020.2.1117",
"version": "2020.2.1317",
"forQualities": [
"insider"
],
+78 -6
View File
@@ -447,6 +447,22 @@
"command": "git.stashDrop",
"title": "%command.stashDrop%",
"category": "Git"
},
{
"command": "git.timeline.openDiff",
"title": "%command.timelineOpenDiff%",
"icon": "$(compare-changes)",
"category": "Git"
},
{
"command": "git.timeline.copyCommitId",
"title": "%command.timelineCopyCommitId%",
"category": "Git"
},
{
"command": "git.timeline.copyCommitMessage",
"title": "%command.timelineCopyCommitMessage%",
"category": "Git"
}
],
"menus": {
@@ -718,14 +734,21 @@
{
"command": "git.stashDrop",
"when": "config.git.enabled && gitOpenRepositoryCount != 0"
},
{
"command": "git.timeline.openDiff",
"when": "false"
},
{
"command": "git.timeline.copyCommitId",
"when": "false"
},
{
"command": "git.timeline.copyCommitMessage",
"when": "false"
}
],
"scm/title": [
{
"command": "git.init",
"group": "navigation",
"when": "config.git.enabled && !scmProvider && gitOpenRepositoryCount == 0 && workspaceFolderCount != 0"
},
{
"command": "git.commit",
"group": "navigation",
@@ -1248,6 +1271,28 @@
"command": "git.revertChange",
"when": "originalResourceScheme == git"
}
],
"timeline/item/context": [
{
"command": "git.timeline.openDiff",
"group": "inline",
"when": "timelineItem =~ /git:file\\b/"
},
{
"command": "git.timeline.openDiff",
"group": "1_timeline",
"when": "timelineItem =~ /git:file\\b/"
},
{
"command": "git.timeline.copyCommitId",
"group": "2_timeline@1",
"when": "timelineItem =~ /git:file:commit\\b/"
},
{
"command": "git.timeline.copyCommitMessage",
"group": "2_timeline@2",
"when": "timelineItem =~ /git:file:commit\\b/"
}
]
},
"configuration": {
@@ -1761,7 +1806,34 @@
72
]
}
}
},
"viewsWelcome": [
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.disabled%",
"when": "!config.git.enabled"
},
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.missing%",
"when": "config.git.enabled && git.missing"
},
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.empty%",
"when": "config.git.enabled && !git.missing && workbenchState == empty"
},
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.folder%",
"when": "config.git.enabled && !git.missing && workbenchState == folder"
},
{
"view": "workbench.scm",
"contents": "%view.workbench.scm.workspace%",
"when": "config.git.enabled && !git.missing && workbenchState == workspace"
}
]
},
"dependencies": {
"byline": "^5.0.0",
+10 -2
View File
@@ -70,6 +70,9 @@
"command.stashApply": "Apply Stash...",
"command.stashApplyLatest": "Apply Latest Stash",
"command.stashDrop": "Drop Stash...",
"command.timelineOpenDiff": "Open Changes",
"command.timelineCopyCommitId": "Copy Commit ID",
"command.timelineCopyCommitMessage": "Copy Commit Message",
"config.enabled": "Whether git is enabled.",
"config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).",
"config.autoRepositoryDetection": "Configures when repositories should be automatically detected.",
@@ -127,7 +130,7 @@
"config.showProgress": "Controls whether git actions should show progress.",
"config.rebaseWhenSync": "Force git to use rebase when running the sync command.",
"config.confirmEmptyCommits": "Always confirm the creation of empty commits for the 'Git: Commit Empty' command.",
"config.fetchOnPull": "Fetch all branches when pulling or just the current one.",
"config.fetchOnPull": "When enabled, fetch all branches when pulling. Otherwise, fetch just the current one.",
"config.pullTags": "Fetch all tags when pulling.",
"config.autoStash": "Stash any changes before pulling and restore them after successful pull.",
"config.allowForcePush": "Controls whether force push (with or without lease) is enabled.",
@@ -146,5 +149,10 @@
"colors.untracked": "Color for untracked resources.",
"colors.ignored": "Color for ignored resources.",
"colors.conflict": "Color for resources with conflicts.",
"colors.submodule": "Color for submodule resources."
"colors.submodule": "Color for submodule resources.",
"view.workbench.scm.missing": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use Git and source control in VS Code in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.",
"view.workbench.scm.disabled": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.empty": "In order to use git features, you can open a folder containing a git repository or clone from a URL.\n[Open Folder](command:vscode.openFolder)\n[Clone from URL](command:git.clone)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.folder": "The folder currently open doesn't have a git repository.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
"view.workbench.scm.workspace": "The workspace currently open doesn't have any folders containing git repositories.\n[Initialize Repository](command:git.init)\nTo learn more about how to use Git and source control in VS Code [read our docs](https://aka.ms/vscode-scm)."
}
+32 -7
View File
@@ -6,7 +6,7 @@
import { lstat, Stats } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode';
import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env } from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions } from './api/git';
@@ -17,6 +17,7 @@ import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineC
import { fromGitUri, toGitUri, isGitUri } from './uri';
import { grep, isDescendant, pathEquals } from './util';
import { Log, LogLevel } from './log';
import { GitTimelineItem } from './timelineProvider';
const localize = nls.loadMessageBundle();
@@ -2331,23 +2332,47 @@ export class CommandCenter {
return result && result.stash;
}
@command('git.openDiff', { repository: false })
async openDiff(uri: Uri, lhs: string, rhs: string) {
@command('git.timeline.openDiff', { repository: false })
async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) {
// eslint-disable-next-line eqeqeq
if (uri == null || !GitTimelineItem.is(item)) {
return undefined;
}
const basename = path.basename(uri.fsPath);
let title;
if ((lhs === 'HEAD' || lhs === '~') && rhs === '') {
if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') {
title = `${basename} (Working Tree)`;
}
else if (lhs === 'HEAD' && rhs === '~') {
else if (item.previousRef === 'HEAD' && item.ref === '~') {
title = `${basename} (Index)`;
} else {
title = `${basename} (${lhs.endsWith('^') ? `${lhs.substr(0, 8)}^` : lhs.substr(0, 8)}) \u27f7 ${basename} (${rhs.endsWith('^') ? `${rhs.substr(0, 8)}^` : rhs.substr(0, 8)})`;
title = `${basename} (${item.shortPreviousRef}) \u27f7 ${basename} (${item.shortRef})`;
}
return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), title);
return commands.executeCommand('vscode.diff', toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title);
}
@command('git.timeline.copyCommitId', { repository: false })
async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) {
if (!GitTimelineItem.is(item)) {
return;
}
env.clipboard.writeText(item.ref);
}
@command('git.timeline.copyCommitMessage', { repository: false })
async timelineCopyCommitMessage(item: TimelineItem, _uri: Uri | undefined, _source: string) {
if (!GitTimelineItem.is(item)) {
return;
}
env.clipboard.writeText(item.message);
}
private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
let result: Promise<any>;
+1
View File
@@ -175,6 +175,7 @@ export async function activate(context: ExtensionContext): Promise<GitExtension>
console.warn(err.message);
outputChannel.appendLine(err.message);
commands.executeCommand('setContext', 'git.missing', true);
warnAboutMissingGit();
return new GitExtensionImpl();
+67 -34
View File
@@ -5,19 +5,62 @@
import * as dayjs from 'dayjs';
import * as advancedFormat from 'dayjs/plugin/advancedFormat';
import * as relativeTime from 'dayjs/plugin/relativeTime';
import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode';
import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineCursor, TimelineItem, TimelineProvider, Uri, workspace } from 'vscode';
import { Model } from './model';
import { Repository } from './repository';
import { debounce } from './decorators';
import { Status } from './api/git';
dayjs.extend(advancedFormat);
dayjs.extend(relativeTime);
// TODO[ECA]: Localize all the strings
// TODO[ECA]: Localize or use a setting for date format
export class GitTimelineItem extends TimelineItem {
static is(item: TimelineItem): item is GitTimelineItem {
return item instanceof GitTimelineItem;
}
readonly ref: string;
readonly previousRef: string;
readonly message: string;
constructor(
ref: string,
previousRef: string,
message: string,
timestamp: number,
id: string,
contextValue: string
) {
const index = message.indexOf('\n');
const label = index !== -1 ? `${message.substring(0, index)} \u2026` : message;
super(label, timestamp);
this.ref = ref;
this.previousRef = previousRef;
this.message = message;
this.id = id;
this.contextValue = contextValue;
}
get shortRef() {
return this.shortenRef(this.ref);
}
get shortPreviousRef() {
return this.shortenRef(this.previousRef);
}
private shortenRef(ref: string): string {
if (ref === '' || ref === '~' || ref === 'HEAD') {
return ref;
}
return ref.endsWith('^') ? `${ref.substr(0, 8)}^` : ref.substr(0, 8);
}
}
export class GitTimelineProvider implements TimelineProvider {
private _onDidChange = new EventEmitter<TimelineChangeEvent>();
get onDidChange(): Event<TimelineChangeEvent> {
@@ -44,7 +87,7 @@ export class GitTimelineProvider implements TimelineProvider {
this._disposable.dispose();
}
async provideTimeline(uri: Uri, _token: CancellationToken): Promise<TimelineItem[]> {
async provideTimeline(uri: Uri, _cursor: TimelineCursor, _token: CancellationToken): Promise<Timeline> {
// console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`);
const repo = this._model.getRepository(uri);
@@ -53,7 +96,7 @@ export class GitTimelineProvider implements TimelineProvider {
this._repoStatusDate = undefined;
this._repo = undefined;
return [];
return { items: [] };
}
if (this._repo?.root !== repo.root) {
@@ -72,25 +115,17 @@ export class GitTimelineProvider implements TimelineProvider {
const commits = await repo.logFile(uri);
let dateFormatter: dayjs.Dayjs;
const items = commits.map<TimelineItem>(c => {
let message = c.message;
const index = message.indexOf('\n');
if (index !== -1) {
message = `${message.substring(0, index)} \u2026`;
}
const items = commits.map<GitTimelineItem>(c => {
dateFormatter = dayjs(c.authorDate);
const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0);
item.id = c.hash;
const item = new GitTimelineItem(c.hash, `${c.hash}^`, c.message, c.authorDate?.getTime() ?? 0, c.hash, 'git:file:commit');
item.iconPath = new (ThemeIcon as any)('git-commit');
item.description = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`;
item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`;
item.description = c.authorName;
item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n\n${c.message}`;
item.command = {
title: 'Open Diff',
command: 'git.openDiff',
arguments: [uri, `${c.hash}^`, c.hash]
title: 'Open Comparison',
command: 'git.timeline.openDiff',
arguments: [uri, this.id, item]
};
return item;
@@ -123,16 +158,15 @@ export class GitTimelineProvider implements TimelineProvider {
break;
}
const item = new TimelineItem('Staged Changes', date.getTime());
item.id = 'index';
const item = new GitTimelineItem('~', 'HEAD', 'Staged Changes', date.getTime(), 'index', 'git:file:index');
// TODO[ECA]: Replace with a better icon -- reflecting its status maybe?
item.iconPath = new (ThemeIcon as any)('git-commit');
item.description = `${dateFormatter.fromNow()} \u2022 You`;
item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`;
item.description = 'You';
item.detail = `You \u2014 Index\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`;
item.command = {
title: 'Open Comparison',
command: 'git.openDiff',
arguments: [uri, 'HEAD', '~']
command: 'git.timeline.openDiff',
arguments: [uri, this.id, item]
};
items.push(item);
@@ -166,22 +200,21 @@ export class GitTimelineProvider implements TimelineProvider {
break;
}
const item = new TimelineItem('Uncommited Changes', date.getTime());
item.id = 'working';
const item = new GitTimelineItem('', index ? '~' : 'HEAD', 'Uncommited Changes', date.getTime(), 'working', 'git:file:working');
// TODO[ECA]: Replace with a better icon -- reflecting its status maybe?
item.iconPath = new (ThemeIcon as any)('git-commit');
item.description = `${dateFormatter.fromNow()} \u2022 You`;
item.detail = `You \u2014 Working Tree\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`;
item.description = 'You';
item.detail = `You \u2014 Working Tree\n${dateFormatter.format('MMMM Do, YYYY h:mma')}\n${status}`;
item.command = {
title: 'Open Comparison',
command: 'git.openDiff',
arguments: [uri, index ? '~' : 'HEAD', '']
command: 'git.timeline.openDiff',
arguments: [uri, this.id, item]
};
items.push(item);
}
return items;
return { items: items };
}
private onRepositoriesChanged(_repo: Repository) {
@@ -208,6 +241,6 @@ export class GitTimelineProvider implements TimelineProvider {
@debounce(500)
private fireChanged() {
this._onDidChange.fire();
this._onDidChange.fire({});
}
}
+14 -19
View File
@@ -382,7 +382,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0:
har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@@ -697,17 +697,12 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
psl@^1.1.24:
psl@^1.1.28:
version "1.7.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0:
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -723,9 +718,9 @@ querystringify@^2.1.1:
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.8.0"
@@ -734,7 +729,7 @@ request@^2.88.0:
extend "~3.0.2"
forever-agent "~0.6.1"
form-data "~2.3.2"
har-validator "~5.1.0"
har-validator "~5.1.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
@@ -744,7 +739,7 @@ request@^2.88.0:
performance-now "^2.1.0"
qs "~6.5.2"
safe-buffer "^5.1.2"
tough-cookie "~2.4.3"
tough-cookie "~2.5.0"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
@@ -822,13 +817,13 @@ supports-color@5.4.0:
dependencies:
has-flag "^3.0.0"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
psl "^1.1.28"
punycode "^2.1.1"
tunnel-agent@^0.6.0:
version "0.6.0"
@@ -99,6 +99,14 @@ export class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLens
case PConst.Kind.memberSetAccessor:
case PConst.Kind.constructorImplementation:
case PConst.Kind.memberVariable:
// Don't show if child and parent have same start
// For https://github.com/microsoft/vscode/issues/90396
if (parent &&
typeConverters.Position.fromLocation(parent.spans[0].start).isEqual(typeConverters.Position.fromLocation(item.spans[0].start))
) {
return null;
}
// Only show if parent is a class type object (not a literal)
switch (parent?.kind) {
case PConst.Kind.class:
@@ -91,6 +91,19 @@ suite('TypeScript References', () => {
const codeLenses = await getCodeLenses(testDocumentUri);
assert.strictEqual(codeLenses?.length, 0);
});
test('Should not show duplicate references on ES5 class (https://github.com/microsoft/vscode/issues/90396)', async () => {
const testDocumentUri = vscode.Uri.parse('untitled:test3.js');
await createTestEditor(testDocumentUri,
`function A() {`,
` console.log("hi");`,
`}`,
`A.x = {};`,
);
const codeLenses = await getCodeLenses(testDocumentUri);
assert.strictEqual(codeLenses?.length, 1);
});
});
function getCodeLenses(document: vscode.Uri): Thenable<readonly vscode.CodeLens[] | undefined> {
@@ -3,11 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'mocha';
import * as assert from 'assert';
import * as vscode from 'vscode';
import 'mocha';
import * as os from 'os';
import { join } from 'path';
import { closeAllEditors, disposeAll, conditionalTest } from '../utils';
import * as vscode from 'vscode';
import { closeAllEditors, conditionalTest, delay, disposeAll } from '../utils';
const webviewId = 'myWebview';
@@ -332,8 +333,30 @@ suite('Webview tests', () => {
webview.webview.postMessage({ value: 1 });
await firstResponse;
assert.strictEqual(webview.viewColumn, vscode.ViewColumn.One);
});
if (os.platform() === 'darwin') {
conditionalTest('webview can copy text from webview', async () => {
const expectedText = `webview text from: ${Date.now()}!`;
const webview = _register(vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, { enableScripts: true, retainContextWhenHidden: true }));
const ready = getMesssage(webview);
webview.webview.html = createHtmlDocumentWithBody(/*html*/`
<b>${expectedText}</b>
<script>
const vscode = acquireVsCodeApi();
document.execCommand('selectAll');
vscode.postMessage({ type: 'ready' });
</script>`);
await ready;
await vscode.commands.executeCommand('editor.action.webvieweditor.copy');
await delay(200); // Make sure copy has time to reach webview
assert.strictEqual(await vscode.env.clipboard.readText(), expectedText);
});
}
});
function createHtmlDocumentWithBody(body: string): string {
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, env, UIKind } from 'vscode';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
@@ -146,12 +146,6 @@ suite('window namespace tests', () => {
});
test('active editor not always correct... #49125', async function () {
if (env.uiKind === UIKind.Web) {
// https://github.com/microsoft/vscode/issues/90470
// https://github.com/microsoft/playwright/issues/979
this.skip();
return;
}
const randomFile1 = await createRandomFile();
const randomFile2 = await createRandomFile();
@@ -215,13 +215,6 @@ suite('workspace-namespace', () => {
});
test('eol, change via onWillSave', async function () {
if (vscode.env.uiKind === vscode.UIKind.Web) {
// TODO@Jo Test seems to fail when running in web due to
// onWillSaveTextDocument not getting called
this.skip();
return;
}
let called = false;
let sub = vscode.workspace.onWillSaveTextDocument(e => {
called = true;
@@ -245,26 +238,32 @@ suite('workspace-namespace', () => {
sub.dispose();
});
function assertEqualPath(a: string, b: string): void {
assert.ok(pathEquals(a, b), `${a} <-> ${b}`);
}
test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => {
const file = await createRandomFile();
let disposables: vscode.Disposable[] = [];
await vscode.workspace.saveAll();
let pendingAsserts: Function[] = [];
let onDidOpenTextDocument = false;
disposables.push(vscode.workspace.onDidOpenTextDocument(e => {
pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath)));
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidOpenTextDocument = true;
}));
let onDidChangeTextDocument = false;
disposables.push(vscode.workspace.onDidChangeTextDocument(e => {
pendingAsserts.push(() => assert.ok(pathEquals(e.document.uri.fsPath, file.fsPath)));
pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath));
onDidChangeTextDocument = true;
}));
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath)));
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
@@ -291,7 +290,7 @@ suite('workspace-namespace', () => {
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
pendingAsserts.push(() => assert.ok(pathEquals(e.uri.fsPath, file.fsPath)));
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.43.0",
"distro": "a87981a5cc52fed24735d447e954adccb9c9f914",
"distro": "821d61f0e38d2eb3e02bec3962fe61e4e5a61527",
"author": {
"name": "Microsoft Corporation"
},
@@ -139,7 +139,7 @@
"opn": "^6.0.0",
"optimist": "0.3.5",
"p-all": "^1.0.0",
"playwright": "^0.10.0",
"playwright": "0.11.0",
"pump": "^1.0.1",
"queue": "3.0.6",
"rcedit": "^1.1.0",
+1 -1
View File
@@ -50,7 +50,7 @@ function code() {
export VSCODE_LOGS=
# Launch Code
exec "$CODE" . "$@"
exec "$CODE" . --no-sandbox "$@"
}
function code-wsl()
+7 -1
View File
@@ -82,7 +82,7 @@ app.once('ready', function () {
traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'
};
contentTracing.startRecording(traceOptions, () => onReady());
contentTracing.startRecording(traceOptions).finally(() => onReady());
} else {
onReady();
}
@@ -133,6 +133,9 @@ function configureCommandlineSwitchesSync(cliArgs) {
// provided by Electron
'disable-color-correct-rendering'
];
if (process.platform === 'linux') {
SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility');
}
// Read argv config
const argvConfig = readArgvConfigSync();
@@ -159,6 +162,9 @@ function configureCommandlineSwitchesSync(cliArgs) {
app.commandLine.appendSwitch('js-flags', jsFlags);
}
// TODO@Ben TODO@Deepak Electron 7 workaround for https://github.com/microsoft/vscode/issues/88873
app.commandLine.appendSwitch('disable-features', 'LayoutNG');
return argvConfig;
}
@@ -5,7 +5,7 @@
@font-face {
font-family: "codicon";
src: url("./codicon.ttf?6caeeccc06315e827f3bff83885456fb") format("truetype");
src: url("./codicon.ttf?d0510f6ecacbb2788db2b3162273a3d8") format("truetype");
}
.codicon[class*='codicon-'] {
+47
View File
@@ -5,6 +5,53 @@
import { pad } from './strings';
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;
// TODO[ECA]: Localize strings
export function fromNow(date: number | Date) {
if (typeof date !== 'number') {
date = date.getTime();
}
const seconds = Math.round((new Date().getTime() - date) / 1000);
if (seconds < 30) {
return 'now';
}
let value: number;
let unit: string;
if (seconds < minute) {
value = seconds;
unit = 'sec';
} else if (seconds < hour) {
value = Math.floor(seconds / minute);
unit = 'min';
} else if (seconds < day) {
value = Math.floor(seconds / hour);
unit = 'hr';
} else if (seconds < week) {
value = Math.floor(seconds / day);
unit = 'day';
} else if (seconds < month) {
value = Math.floor(seconds / week);
unit = 'wk';
} else if (seconds < year) {
value = Math.floor(seconds / month);
unit = 'mo';
} else {
value = Math.floor(seconds / year);
unit = 'yr';
}
return `${value} ${unit}${value === 1 ? '' : 's'}`;
}
export function toLocalISOString(date: Date): string {
return date.getFullYear() +
'-' + pad(date.getMonth() + 1, 2) +
@@ -3,6 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { memoize } from 'vs/base/common/decorators';
export interface ILink {
readonly label: string;
readonly href: string;
@@ -10,7 +12,16 @@ export interface ILink {
}
export type LinkedTextNode = string | ILink;
export type LinkedText = LinkedTextNode[];
export class LinkedText {
constructor(readonly nodes: LinkedTextNode[]) { }
@memoize
toString(): string {
return this.nodes.map(node => typeof node === 'string' ? node : node.label).join('');
}
}
const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi;
@@ -40,5 +51,5 @@ export function parseLinkedText(text: string): LinkedText {
result.push(text.substring(index));
}
return result;
return new LinkedText(result);
}
@@ -4,50 +4,50 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { parseLinkedText } from 'vs/base/browser/linkedText';
import { parseLinkedText } from 'vs/base/common/linkedText';
suite('LinkedText', () => {
test('parses correctly', () => {
assert.deepEqual(parseLinkedText(''), []);
assert.deepEqual(parseLinkedText('hello'), ['hello']);
assert.deepEqual(parseLinkedText('hello there'), ['hello there']);
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).'), [
assert.deepEqual(parseLinkedText('').nodes, []);
assert.deepEqual(parseLinkedText('hello').nodes, ['hello']);
assert.deepEqual(parseLinkedText('hello there').nodes, ['hello there']);
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href).').nodes, [
'Some message with ',
{ label: 'link text', href: 'http://link.href' },
'.'
]);
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").'), [
assert.deepEqual(parseLinkedText('Some message with [link text](http://link.href "and a title").').nodes, [
'Some message with ',
{ label: 'link text', href: 'http://link.href', title: 'and a title' },
'.'
]);
assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).'), [
assert.deepEqual(parseLinkedText('Some message with [link text](random stuff).').nodes, [
'Some message with [link text](random stuff).'
]);
assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).'), [
assert.deepEqual(parseLinkedText('Some message with [https link](https://link.href).').nodes, [
'Some message with ',
{ label: 'https link', href: 'https://link.href' },
'.'
]);
assert.deepEqual(parseLinkedText('Some message with [https link](https:).'), [
assert.deepEqual(parseLinkedText('Some message with [https link](https:).').nodes, [
'Some message with [https link](https:).'
]);
assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).'), [
assert.deepEqual(parseLinkedText('Some message with [a command](command:foobar).').nodes, [
'Some message with ',
{ label: 'a command', href: 'command:foobar' },
'.'
]);
assert.deepEqual(parseLinkedText('Some message with [a command](command:).'), [
assert.deepEqual(parseLinkedText('Some message with [a command](command:).').nodes, [
'Some message with [a command](command:).'
]);
assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...'), [
assert.deepEqual(parseLinkedText('link [one](command:foo "nice") and link [two](http://foo)...').nodes, [
'link ',
{ label: 'one', href: 'command:foo', title: 'nice' },
' and link ',
{ label: 'two', href: 'http://foo' },
'...'
]);
assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...'), [
assert.deepEqual(parseLinkedText('link\n[one](command:foo "nice")\nand link [two](http://foo)...').nodes, [
'link\n',
{ label: 'one', href: 'command:foo', title: 'nice' },
'\nand link ',
+20 -35
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLinkProvider, IApplicationLink } from 'vs/workbench/workbench.web.api';
import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider, IWorkspaceProvider, IWorkspace, IApplicationLink } from 'vs/workbench/workbench.web.api';
import { generateUuid } from 'vs/base/common/uuid';
import { CancellationToken } from 'vs/base/common/cancellation';
import { streamToBuffer } from 'vs/base/common/buffer';
@@ -279,39 +279,6 @@ class WorkspaceProvider implements IWorkspaceProvider {
}
}
class ApplicationLinkProvider {
private links: IApplicationLink[] | undefined = undefined;
constructor(workspace: IWorkspace) {
this.computeLink(workspace);
}
private computeLink(workspace: IWorkspace): void {
if (!workspace) {
return; // not for empty workspaces
}
const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined;
if (workspaceUri) {
this.links = [{
uri: URI.from({
scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders',
authority: Schemas.vscodeRemote,
path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path),
query: workspaceUri.query,
fragment: workspaceUri.fragment,
}),
label: localize('openInDesktop', "Open in Desktop")
}];
}
}
get provider(): IApplicationLinkProvider {
return () => this.links;
}
}
(function () {
// Find config by checking for DOM
@@ -375,12 +342,30 @@ class ApplicationLinkProvider {
}
}
// Application links ("Open in Desktop")
let applicationLinks: IApplicationLink[] | undefined = undefined;
if (workspace) {
const workspaceUri = isWorkspaceToOpen(workspace) ? workspace.workspaceUri : isFolderToOpen(workspace) ? workspace.folderUri : undefined;
if (workspaceUri) {
applicationLinks = [{
uri: URI.from({
scheme: product.quality === 'stable' ? 'vscode' : 'vscode-insiders',
authority: Schemas.vscodeRemote,
path: posix.join(posix.sep, workspaceUri.authority, workspaceUri.path),
query: workspaceUri.query,
fragment: workspaceUri.fragment,
}),
label: localize('openInDesktop', "Open in Desktop")
}];
}
}
// Finally create workbench
create(document.body, {
...config,
workspaceProvider: new WorkspaceProvider(workspace, payload),
urlCallbackProvider: new PollingURLCallbackProvider(),
credentialsProvider: new LocalStorageCredentialsProvider(),
applicationLinkProvider: new ApplicationLinkProvider(workspace).provider
applicationLinks: applicationLinks
});
})();
@@ -149,6 +149,10 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe
return result;
}
const enum Constants {
SPAN_MODULO_LIMIT = 16384
}
function renderLine(lineContent: string, initialVisibleColumn: number, tabSize: number, width: number, sb: IStringBuilder): [number[], number[]] {
sb.appendASCIIString('<div style="width:');
sb.appendASCIIString(String(width));
@@ -164,7 +168,11 @@ function renderLine(lineContent: string, initialVisibleColumn: number, tabSize:
let visibleColumns: number[] = [];
let nextCharCode = (0 < len ? lineContent.charCodeAt(0) : CharCode.Null);
sb.appendASCIIString('<span>');
for (let charIndex = 0; charIndex < len; charIndex++) {
if (charIndex !== 0 && charIndex % Constants.SPAN_MODULO_LIMIT === 0) {
sb.appendASCIIString('</span><span>');
}
charOffsets[charIndex] = charOffset;
visibleColumns[charIndex] = visibleColumn;
const charCode = nextCharCode;
@@ -227,6 +235,7 @@ function renderLine(lineContent: string, initialVisibleColumn: number, tabSize:
charOffset += producedCharacters;
visibleColumn += charWidth;
}
sb.appendASCIIString('</span>');
charOffsets[lineContent.length] = charOffset;
visibleColumns[lineContent.length] = visibleColumn;
@@ -240,10 +249,15 @@ function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent:
if (lineContent.length <= 1) {
return null;
}
const textContentNode = lineDomNode.firstChild!;
const spans = <HTMLSpanElement[]>Array.prototype.slice.call(lineDomNode.children, 0);
const breakOffsets: number[] = [];
discoverBreaks(range, textContentNode, charOffsets, 0, null, lineContent.length - 1, null, breakOffsets);
try {
discoverBreaks(range, spans, charOffsets, 0, null, lineContent.length - 1, null, breakOffsets);
} catch (err) {
console.log(err);
return null;
}
if (breakOffsets.length === 0) {
return null;
@@ -255,13 +269,13 @@ function readLineBreaks(range: Range, lineDomNode: HTMLDivElement, lineContent:
type MaybeRects = ClientRectList | DOMRectList | null;
function discoverBreaks(range: Range, textContentNode: Node, charOffsets: number[], low: number, lowRects: MaybeRects, high: number, highRects: MaybeRects, result: number[]): void {
function discoverBreaks(range: Range, spans: HTMLSpanElement[], charOffsets: number[], low: number, lowRects: MaybeRects, high: number, highRects: MaybeRects, result: number[]): void {
if (low === high) {
return;
}
lowRects = lowRects || readClientRect(range, textContentNode, charOffsets[low], charOffsets[low + 1]);
highRects = highRects || readClientRect(range, textContentNode, charOffsets[high], charOffsets[high + 1]);
lowRects = lowRects || readClientRect(range, spans, charOffsets[low], charOffsets[low + 1]);
highRects = highRects || readClientRect(range, spans, charOffsets[high], charOffsets[high + 1]);
if (Math.abs(lowRects[0].top - highRects[0].top) <= 0.1) {
// same line
@@ -276,13 +290,13 @@ function discoverBreaks(range: Range, textContentNode: Node, charOffsets: number
}
const mid = low + ((high - low) / 2) | 0;
const midRects = readClientRect(range, textContentNode, charOffsets[mid], charOffsets[mid + 1]);
discoverBreaks(range, textContentNode, charOffsets, low, lowRects, mid, midRects, result);
discoverBreaks(range, textContentNode, charOffsets, mid, midRects, high, highRects, result);
const midRects = readClientRect(range, spans, charOffsets[mid], charOffsets[mid + 1]);
discoverBreaks(range, spans, charOffsets, low, lowRects, mid, midRects, result);
discoverBreaks(range, spans, charOffsets, mid, midRects, high, highRects, result);
}
function readClientRect(range: Range, textContentNode: Node, startOffset: number, endOffset: number): ClientRectList | DOMRectList {
range.setStart(textContentNode, startOffset);
range.setEnd(textContentNode, endOffset);
function readClientRect(range: Range, spans: HTMLSpanElement[], startOffset: number, endOffset: number): ClientRectList | DOMRectList {
range.setStart(spans[(startOffset / Constants.SPAN_MODULO_LIMIT) | 0].firstChild!, startOffset % Constants.SPAN_MODULO_LIMIT);
range.setEnd(spans[(endOffset / Constants.SPAN_MODULO_LIMIT) | 0].firstChild!, endOffset % Constants.SPAN_MODULO_LIMIT);
return range.getClientRects();
}
File diff suppressed because it is too large Load Diff
@@ -34,11 +34,13 @@ export class MinimapCharRenderer {
color: RGBA8,
backgroundColor: RGBA8,
fontScale: number,
useLighterFont: boolean
useLighterFont: boolean,
force1pxHeight: boolean
): void {
const charWidth = Constants.BASE_CHAR_WIDTH * this.scale;
const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale;
if (dx + charWidth > target.width || dy + charHeight > target.height) {
const renderHeight = (force1pxHeight ? 1 : charHeight);
if (dx + charWidth > target.width || dy + renderHeight > target.height) {
console.warn('bad render request outside image data');
return;
}
@@ -60,7 +62,7 @@ export class MinimapCharRenderer {
let sourceOffset = charIndex * charWidth * charHeight;
let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT;
for (let y = 0; y < charHeight; y++) {
for (let y = 0; y < renderHeight; y++) {
let column = row;
for (let x = 0; x < charWidth; x++) {
const c = charData[sourceOffset++] / 255;
@@ -80,11 +82,13 @@ export class MinimapCharRenderer {
dy: number,
color: RGBA8,
backgroundColor: RGBA8,
useLighterFont: boolean
useLighterFont: boolean,
force1pxHeight: boolean
): void {
const charWidth = Constants.BASE_CHAR_WIDTH * this.scale;
const charHeight = Constants.BASE_CHAR_HEIGHT * this.scale;
if (dx + charWidth > target.width || dy + charHeight > target.height) {
const renderHeight = (force1pxHeight ? 1 : charHeight);
if (dx + charWidth > target.width || dy + renderHeight > target.height) {
console.warn('bad render request outside image data');
return;
}
@@ -108,7 +112,7 @@ export class MinimapCharRenderer {
const dest = target.data;
let row = dy * destWidth + dx * Constants.RGBA_CHANNELS_CNT;
for (let y = 0; y < charHeight; y++) {
for (let y = 0; y < renderHeight; y++) {
let column = row;
for (let x = 0; x < charWidth; x++) {
dest[column++] = colorR;
@@ -276,7 +276,10 @@ export class DecorationsOverviewRuler extends ViewPart {
return true;
}
public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
return true;
if (e.affectsOverviewRuler) {
return true;
}
return false;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
return true;
@@ -287,6 +287,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
public options!: ComputedEditorOptions;
private _isDominatedByLongLines: boolean;
private _maxLineNumber: number;
private _lineNumbersDigitCount: number;
private _rawOptions: IEditorOptions;
@@ -298,6 +299,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
this.isSimpleWidget = isSimpleWidget;
this._isDominatedByLongLines = false;
this._maxLineNumber = 1;
this._lineNumbersDigitCount = 1;
this._rawOptions = deepCloneAndMigrateOptions(_options);
@@ -347,6 +349,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
fontInfo: this.readConfiguration(bareFontInfo),
extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines,
maxLineNumber: this._maxLineNumber,
lineNumbersDigitCount: this._lineNumbersDigitCount,
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio,
@@ -405,11 +408,11 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
}
public setMaxLineNumber(maxLineNumber: number): void {
let digitCount = CommonEditorConfiguration._digitCount(maxLineNumber);
if (this._lineNumbersDigitCount === digitCount) {
if (this._maxLineNumber === maxLineNumber) {
return;
}
this._lineNumbersDigitCount = digitCount;
this._maxLineNumber = maxLineNumber;
this._lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber);
this._recomputeOptions();
}
+108 -6
View File
@@ -672,6 +672,7 @@ export interface IEnvironmentalOptions {
readonly fontInfo: FontInfo;
readonly extraEditorClassName: string;
readonly isDominatedByLongLines: boolean;
readonly maxLineNumber: number;
readonly lineNumbersDigitCount: number;
readonly emptySelectionClipboard: boolean;
readonly pixelRatio: number;
@@ -1685,6 +1686,14 @@ export interface EditorLayoutInfo {
* The width of the minimap
*/
readonly minimapWidth: number;
readonly minimapHeightIsEditorHeight: boolean;
readonly minimapIsSampling: boolean;
readonly minimapScale: number;
readonly minimapLineHeight: number;
readonly minimapCanvasInnerWidth: number;
readonly minimapCanvasInnerHeight: number;
readonly minimapCanvasOuterWidth: number;
readonly minimapCanvasOuterHeight: number;
/**
* Minimap render type
@@ -1718,6 +1727,7 @@ export interface EditorLayoutInfoComputerEnv {
outerWidth: number;
outerHeight: number;
lineHeight: number;
maxLineNumber: number;
lineNumbersDigitCount: number;
typicalHalfwidthCharacterWidth: number;
maxDigitWidth: number;
@@ -1741,6 +1751,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
outerWidth: env.outerWidth,
outerHeight: env.outerHeight,
lineHeight: env.fontInfo.lineHeight,
maxLineNumber: env.maxLineNumber,
lineNumbersDigitCount: env.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth,
maxDigitWidth: env.fontInfo.maxDigitWidth,
@@ -1748,6 +1759,20 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
});
}
public static computeContainedMinimapLineCount(input: {
modelLineCount: number;
scrollBeyondLastLine: boolean;
height: number;
lineHeight: number;
pixelRatio: number;
}): { typicalViewportLineCount: number; extraLinesBeyondLastLine: number; desiredRatio: number; minimapLineCount: number; } {
const typicalViewportLineCount = input.height / input.lineHeight;
const extraLinesBeyondLastLine = input.scrollBeyondLastLine ? (typicalViewportLineCount - 1) : 0;
const desiredRatio = (input.modelLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height);
const minimapLineCount = Math.floor(input.modelLineCount / desiredRatio);
return { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount };
}
public static computeLayout(options: IComputedEditorOptions, env: EditorLayoutInfoComputerEnv): EditorLayoutInfo {
const outerWidth = env.outerWidth | 0;
const outerHeight = env.outerHeight | 0;
@@ -1760,12 +1785,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
const showGlyphMargin = options.get(EditorOption.glyphMargin);
const showLineNumbers = (options.get(EditorOption.lineNumbers).renderType !== RenderLineNumbersType.Off);
const lineNumbersMinChars = options.get(EditorOption.lineNumbersMinChars) | 0;
const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine);
const minimap = options.get(EditorOption.minimap);
const minimapEnabled = minimap.enabled;
const minimapSide = minimap.side;
const minimapRenderCharacters = minimap.renderCharacters;
const minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale);
let minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale);
const minimapMaxColumn = minimap.maxColumn | 0;
const minimapMode = minimap.mode;
const scrollbar = options.get(EditorOption.scrollbar);
const verticalScrollbarWidth = scrollbar.verticalScrollbarSize | 0;
@@ -1805,19 +1832,65 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
const remainingWidth = outerWidth - glyphMarginWidth - lineNumbersWidth - lineDecorationsWidth;
const baseCharHeight = minimapRenderCharacters ? 2 : 3;
let renderMinimap: RenderMinimap;
let minimapLeft: number;
let minimapWidth: number;
let minimapCanvasInnerWidth: number;
let minimapCanvasInnerHeight = Math.floor(pixelRatio * outerHeight);
let minimapCanvasOuterWidth: number;
const minimapCanvasOuterHeight = minimapCanvasInnerHeight / pixelRatio;
let minimapHeightIsEditorHeight = false;
let minimapIsSampling = false;
let minimapLineHeight = baseCharHeight * minimapScale;
let contentWidth: number;
if (!minimapEnabled) {
minimapLeft = 0;
minimapWidth = 0;
minimapCanvasInnerWidth = 0;
minimapCanvasOuterWidth = 0;
minimapLineHeight = 1;
renderMinimap = RenderMinimap.None;
contentWidth = remainingWidth;
} else {
// The minimapScale is also the pixel width of each character. Adjust
// for the pixel ratio of the screen.
const minimapCharWidth = minimapScale / pixelRatio;
let minimapCharWidth = minimapScale / pixelRatio;
let minimapWidthMultiplier: number = 1;
if (minimapMode === 'cover' || minimapMode === 'contain') {
const modelLineCount = env.maxLineNumber;
const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
modelLineCount: modelLineCount,
scrollBeyondLastLine: scrollBeyondLastLine,
height: outerHeight,
lineHeight: lineHeight,
pixelRatio: pixelRatio
});
// ratio is intentionally not part of the layout to avoid the layout changing all the time
// when doing sampling
const ratio = modelLineCount / minimapLineCount;
if (ratio > 1) {
minimapHeightIsEditorHeight = true;
minimapIsSampling = true;
minimapScale = 1;
minimapLineHeight = 1;
minimapCharWidth = minimapScale / pixelRatio;
} else {
const effectiveMinimapHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
if (minimapMode === 'cover' || effectiveMinimapHeight > minimapCanvasInnerHeight) {
minimapHeightIsEditorHeight = true;
const configuredFontScale = minimapScale;
minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio)));
minimapScale = Math.min(configuredFontScale + 1, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight)));
if (minimapScale > configuredFontScale) {
minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale);
}
minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, modelLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
}
}
}
renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks;
// Given:
@@ -1849,6 +1922,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
} else {
minimapLeft = outerWidth - minimapWidth - verticalScrollbarWidth;
}
minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth);
minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio;
minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier);
}
// (leaving 2px for the cursor to have space after the last character)
@@ -1875,6 +1952,14 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
renderMinimap: renderMinimap,
minimapLeft: minimapLeft,
minimapWidth: minimapWidth,
minimapHeightIsEditorHeight: minimapHeightIsEditorHeight,
minimapIsSampling: minimapIsSampling,
minimapScale: minimapScale,
minimapLineHeight: minimapLineHeight,
minimapCanvasInnerWidth: minimapCanvasInnerWidth,
minimapCanvasInnerHeight: minimapCanvasInnerHeight,
minimapCanvasOuterWidth: minimapCanvasOuterWidth,
minimapCanvasOuterHeight: minimapCanvasOuterHeight,
viewportColumn: viewportColumn,
@@ -1975,6 +2060,11 @@ export interface IEditorMinimapOptions {
* Defaults to 'right'.
*/
side?: 'right' | 'left';
/**
* Control the minimap rendering mode.
* Defaults to 'actual'.
*/
mode?: 'actual' | 'cover' | 'contain';
/**
* Control the rendering of the minimap slider.
* Defaults to 'mouseover'.
@@ -1990,7 +2080,6 @@ export interface IEditorMinimapOptions {
* Defaults to 120.
*/
maxColumn?: number;
/**
* Relative size of the font in the minimap. Defaults to 1.
*/
@@ -2004,6 +2093,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
constructor() {
const defaults: EditorMinimapOptions = {
enabled: true,
mode: 'actual',
side: 'right',
showSlider: 'mouseover',
renderCharacters: true,
@@ -2018,6 +2108,17 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
default: defaults.enabled,
description: nls.localize('minimap.enabled', "Controls whether the minimap is shown.")
},
'editor.minimap.mode': {
type: 'string',
enum: ['actual', 'cover', 'contain'],
enumDescriptions: [
nls.localize('minimap.mode.actual', "The minimap will be displayed in its original size, so it might be higher than the editor."),
nls.localize('minimap.mode.cover', "The minimap will always have the height of the editor and will stretch or shrink as necessary."),
nls.localize('minimap.mode.contain', "The minimap will shrink as necessary to never be higher than the editor."),
],
default: defaults.mode,
description: nls.localize('minimap.mode', "Controls the rendering mode of the minimap.")
},
'editor.minimap.side': {
type: 'string',
enum: ['left', 'right'],
@@ -2046,7 +2147,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
type: 'number',
default: defaults.maxColumn,
description: nls.localize('minimap.maxColumn', "Limit the width of the minimap to render at most a certain number of columns.")
},
}
}
);
}
@@ -2058,6 +2159,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
const input = _input as IEditorMinimapOptions;
return {
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled),
mode: EditorStringEnumOption.stringSet<'actual' | 'cover' | 'contain'>(input.mode, this.defaultValue.mode, ['actual', 'cover', 'contain']),
side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']),
showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']),
renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters),
+54 -14
View File
@@ -1423,19 +1423,15 @@ export class TextModel extends Disposable implements model.ITextModel {
private _changeDecorations<T>(ownerId: number, callback: (changeAccessor: model.IModelDecorationsChangeAccessor) => T): T | null {
let changeAccessor: model.IModelDecorationsChangeAccessor = {
addDecoration: (range: IRange, options: model.IModelDecorationOptions): string => {
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0];
},
changeDecoration: (id: string, newRange: IRange): void => {
this._onDidChangeDecorations.fire();
this._changeDecorationImpl(id, newRange);
},
changeDecorationOptions: (id: string, options: model.IModelDecorationOptions) => {
this._onDidChangeDecorations.fire();
this._changeDecorationOptionsImpl(id, _normalizeOptions(options));
},
removeDecoration: (id: string): void => {
this._onDidChangeDecorations.fire();
this._deltaDecorationsImpl(ownerId, [id], []);
},
deltaDecorations: (oldDecorations: string[], newDecorations: model.IModelDeltaDecoration[]): string[] => {
@@ -1443,7 +1439,6 @@ export class TextModel extends Disposable implements model.ITextModel {
// nothing to do
return [];
}
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
}
};
@@ -1474,7 +1469,6 @@ export class TextModel extends Disposable implements model.ITextModel {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
} finally {
this._onDidChangeDecorations.endDeferredEmit();
@@ -1622,6 +1616,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorationsTree.delete(node);
node.reset(this.getVersionId(), startOffset, endOffset, range);
this._decorationsTree.insert(node);
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
}
private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void {
@@ -1633,6 +1628,9 @@ export class TextModel extends Disposable implements model.ITextModel {
const nodeWasInOverviewRuler = (node.options.overviewRuler && node.options.overviewRuler.color ? true : false);
const nodeIsInOverviewRuler = (options.overviewRuler && options.overviewRuler.color ? true : false);
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
this._onDidChangeDecorations.checkAffectedAndFire(options);
if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) {
// Delete + Insert due to an overview ruler status change
this._decorationsTree.delete(node);
@@ -1666,6 +1664,7 @@ export class TextModel extends Disposable implements model.ITextModel {
// (2) remove the node from the tree (if it exists)
if (node) {
this._decorationsTree.delete(node);
this._onDidChangeDecorations.checkAffectedAndFire(node.options);
}
}
@@ -1688,6 +1687,7 @@ export class TextModel extends Disposable implements model.ITextModel {
node.ownerId = ownerId;
node.reset(versionId, startOffset, endOffset, range);
node.setOptions(options);
this._onDidChangeDecorations.checkAffectedAndFire(options);
this._decorationsTree.insert(node);
@@ -1713,7 +1713,7 @@ export class TextModel extends Disposable implements model.ITextModel {
throw new Error('Illegal value for lineNumber');
}
this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens);
this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens, false);
}
public setTokens(tokens: MultilineTokens[]): void {
@@ -1725,16 +1725,34 @@ export class TextModel extends Disposable implements model.ITextModel {
for (let i = 0, len = tokens.length; i < len; i++) {
const element = tokens[i];
ranges.push({ fromLineNumber: element.startLineNumber, toLineNumber: element.startLineNumber + element.tokens.length - 1 });
let minChangedLineNumber = 0;
let maxChangedLineNumber = 0;
let hasChange = false;
for (let j = 0, lenJ = element.tokens.length; j < lenJ; j++) {
this.setLineTokens(element.startLineNumber + j, element.tokens[j]);
const lineNumber = element.startLineNumber + j;
if (hasChange) {
this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], false);
maxChangedLineNumber = lineNumber;
} else {
const lineHasChange = this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), element.tokens[j], true);
if (lineHasChange) {
hasChange = true;
minChangedLineNumber = lineNumber;
maxChangedLineNumber = lineNumber;
}
}
}
if (hasChange) {
ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber });
}
}
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
ranges: ranges
});
if (ranges.length > 0) {
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
ranges: ranges
});
}
}
public setSemanticTokens(tokens: MultilineTokens2[] | null): void {
@@ -3083,11 +3101,15 @@ export class DidChangeDecorationsEmitter extends Disposable {
private _deferredCnt: number;
private _shouldFire: boolean;
private _affectsMinimap: boolean;
private _affectsOverviewRuler: boolean;
constructor() {
super();
this._deferredCnt = 0;
this._shouldFire = false;
this._affectsMinimap = false;
this._affectsOverviewRuler = false;
}
public beginDeferredEmit(): void {
@@ -3098,13 +3120,31 @@ export class DidChangeDecorationsEmitter extends Disposable {
this._deferredCnt--;
if (this._deferredCnt === 0) {
if (this._shouldFire) {
const event: IModelDecorationsChangedEvent = {
affectsMinimap: this._affectsMinimap,
affectsOverviewRuler: this._affectsOverviewRuler,
};
this._shouldFire = false;
this._actual.fire({});
this._affectsMinimap = false;
this._affectsOverviewRuler = false;
this._actual.fire(event);
}
}
}
public checkAffectedAndFire(options: ModelDecorationOptions): void {
if (!this._affectsMinimap) {
this._affectsMinimap = options.minimap && options.minimap.position ? true : false;
}
if (!this._affectsOverviewRuler) {
this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false;
}
this._shouldFire = true;
}
public fire(): void {
this._affectsMinimap = true;
this._affectsOverviewRuler = true;
this._shouldFire = true;
}
}
@@ -76,6 +76,8 @@ export interface IModelContentChangedEvent {
* An event describing that model decorations have changed.
*/
export interface IModelDecorationsChangedEvent {
readonly affectsMinimap: boolean;
readonly affectsOverviewRuler: boolean;
}
/**
+26 -1
View File
@@ -964,10 +964,35 @@ export class TokensStore {
this._len += insertCount;
}
public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null): void {
public setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null, checkEquality: boolean): boolean {
const tokens = TokensStore._massageTokens(topLevelLanguageId, lineTextLength, _tokens);
this._ensureLine(lineIndex);
const oldTokens = this._lineTokens[lineIndex];
this._lineTokens[lineIndex] = tokens;
if (checkEquality) {
return !TokensStore._equals(oldTokens, tokens);
}
return false;
}
private static _equals(_a: Uint32Array | ArrayBuffer | null, _b: Uint32Array | ArrayBuffer | null) {
if (!_a || !_b) {
return !_a && !_b;
}
const a = toUint32Array(_a);
const b = toUint32Array(_b);
if (a.length !== b.length) {
return false;
}
for (let i = 0, len = a.length; i < len; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
//#region Editing
+33
View File
@@ -265,6 +265,34 @@ export interface HoverProvider {
provideHover(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
}
/**
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
*/
export interface EvaluatableExpression {
/**
* The range to which this expression applies.
*/
range: IRange;
/*
* This expression overrides the expression extracted from the range.
*/
expression?: string;
}
/**
* The hover provider interface defines the contract between extensions and
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide a hover for the given position and document. Multiple hovers at the same
* position will be merged by the editor. A hover can have a range which defaults
* to the word range at the position when omitted.
*/
provideEvaluatableExpression(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export const enum CompletionItemKind {
Method,
Function,
@@ -1595,6 +1623,11 @@ export const SignatureHelpProviderRegistry = new LanguageFeatureRegistry<Signatu
*/
export const HoverProviderRegistry = new LanguageFeatureRegistry<HoverProvider>();
/**
* @internal
*/
export const EvaluatableExpressionProviderRegistry = new LanguageFeatureRegistry<EvaluatableExpressionProvider>();
/**
* @internal
*/
+12 -2
View File
@@ -10,6 +10,7 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
export const enum ViewEventType {
ViewConfigurationChanged = 1,
@@ -82,8 +83,17 @@ export class ViewDecorationsChangedEvent {
public readonly type = ViewEventType.ViewDecorationsChanged;
constructor() {
// Nothing to do
readonly affectsMinimap: boolean;
readonly affectsOverviewRuler: boolean;
constructor(source: IModelDecorationsChangedEvent | null) {
if (source) {
this.affectsMinimap = source.affectsMinimap;
this.affectsOverviewRuler = source.affectsOverviewRuler;
} else {
this.affectsMinimap = true;
this.affectsOverviewRuler = true;
}
}
}
@@ -172,7 +172,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (this.lines.setWrappingSettings(fontInfo, wrappingStrategy, wrappingInfo.wrappingColumn, wrappingIndent)) {
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
@@ -185,7 +185,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (e.hasChanged(EditorOption.readOnly)) {
// Must read again all decorations due to readOnly filtering
this.decorations.reset();
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
}
eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e));
@@ -291,7 +291,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.decorations.onLineMappingChanged();
}
} finally {
@@ -354,7 +354,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const eventsCollector = this._beginEmit();
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
} finally {
this._endEmit();
}
@@ -365,7 +365,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.decorations.onModelDecorationsChanged();
try {
const eventsCollector = this._beginEmit();
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(e));
} finally {
this._endEmit();
}
@@ -379,7 +379,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (lineMappingChanged) {
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
this.viewLayout.onHeightMaybeChanged();
+3 -2
View File
@@ -419,8 +419,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
if (currentMatch) {
const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn);
const lineContent = this._codeEditor.getModel()?.getLineContent(currentMatch.startLineNumber);
if (lineContent) {
const model = this._codeEditor.getModel();
if (model && (currentMatch.startLineNumber <= model.getLineCount()) && (currentMatch.startLineNumber >= 1)) {
const lineContent = model.getLineContent(currentMatch.startLineNumber);
return `${lineContent}, ${ariaLabel}`;
}
@@ -78,7 +78,7 @@ suite('MinimapCharRenderer', () => {
imageData.data[4 * i + 2] = background.b;
imageData.data[4 * i + 3] = 255;
}
renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false);
renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 2, false, false);
let actual: number[] = [];
for (let i = 0; i < imageData.data.length; i++) {
@@ -108,7 +108,7 @@ suite('MinimapCharRenderer', () => {
imageData.data[4 * i + 3] = 255;
}
renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false);
renderer.renderChar(imageData, 0, 0, 'd'.charCodeAt(0), color, background, 1, false, false);
let actual: number[] = [];
for (let i = 0; i < imageData.data.length; i++) {
@@ -17,6 +17,7 @@ interface IEditorLayoutProviderOpts {
readonly showLineNumbers: boolean;
readonly lineNumbersMinChars: number;
readonly lineNumbersDigitCount: number;
maxLineNumber?: number;
readonly lineDecorationsWidth: number;
@@ -32,6 +33,7 @@ interface IEditorLayoutProviderOpts {
readonly minimapSide: 'left' | 'right';
readonly minimapRenderCharacters: boolean;
readonly minimapMaxColumn: number;
minimapMode?: 'actual' | 'cover' | 'contain';
readonly pixelRatio: number;
}
@@ -45,6 +47,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
options._write(EditorOption.folding, false);
const minimapOptions: EditorMinimapOptions = {
enabled: input.minimap,
mode: input.minimapMode || 'actual',
side: input.minimapSide,
renderCharacters: input.minimapRenderCharacters,
maxColumn: input.minimapMaxColumn,
@@ -77,6 +80,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
outerWidth: input.outerWidth,
outerHeight: input.outerHeight,
lineHeight: input.lineHeight,
maxLineNumber: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
lineNumbersDigitCount: input.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth,
maxDigitWidth: input.maxDigitWidth,
@@ -125,6 +129,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 800,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 800,
viewportColumn: 98,
verticalScrollbarWidth: 0,
@@ -179,6 +191,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 800,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 800,
viewportColumn: 97,
verticalScrollbarWidth: 11,
@@ -233,6 +253,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 800,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 800,
viewportColumn: 88,
verticalScrollbarWidth: 0,
@@ -287,6 +315,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 88,
verticalScrollbarWidth: 0,
@@ -341,6 +377,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 88,
verticalScrollbarWidth: 0,
@@ -395,6 +439,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 83,
verticalScrollbarWidth: 0,
@@ -449,6 +501,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 83,
verticalScrollbarWidth: 0,
@@ -503,6 +563,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 82,
verticalScrollbarWidth: 0,
@@ -557,6 +625,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 171,
verticalScrollbarWidth: 0,
@@ -611,6 +687,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.None,
minimapLeft: 0,
minimapWidth: 0,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 0,
minimapCanvasInnerHeight: 900,
minimapCanvasOuterWidth: 0,
minimapCanvasOuterHeight: 900,
viewportColumn: 169,
verticalScrollbarWidth: 0,
@@ -665,6 +749,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.Text,
minimapLeft: 903,
minimapWidth: 97,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 1,
minimapLineHeight: 2,
minimapCanvasInnerWidth: 97,
minimapCanvasInnerHeight: 800,
minimapCanvasOuterWidth: 97,
minimapCanvasOuterHeight: 800,
viewportColumn: 89,
verticalScrollbarWidth: 0,
@@ -719,6 +811,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.Text,
minimapLeft: 903,
minimapWidth: 97,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 2,
minimapLineHeight: 4,
minimapCanvasInnerWidth: 194,
minimapCanvasInnerHeight: 1600,
minimapCanvasOuterWidth: 97,
minimapCanvasOuterHeight: 800,
viewportColumn: 89,
verticalScrollbarWidth: 0,
@@ -773,6 +873,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.Text,
minimapLeft: 945,
minimapWidth: 55,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 2,
minimapLineHeight: 4,
minimapCanvasInnerWidth: 220,
minimapCanvasInnerHeight: 3200,
minimapCanvasOuterWidth: 55,
minimapCanvasOuterHeight: 800,
viewportColumn: 93,
verticalScrollbarWidth: 0,
@@ -827,6 +935,270 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.Text,
minimapLeft: 0,
minimapWidth: 55,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 2,
minimapLineHeight: 4,
minimapCanvasInnerWidth: 220,
minimapCanvasInnerHeight: 3200,
minimapCanvasOuterWidth: 55,
minimapCanvasOuterHeight: 800,
viewportColumn: 93,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
overviewRuler: {
top: 0,
width: 0,
height: 800,
right: 0
}
});
});
test('EditorLayoutProvider 11 - minimap mode cover without sampling', () => {
doTest({
outerWidth: 1000,
outerHeight: 800,
showGlyphMargin: false,
lineHeight: 16,
showLineNumbers: false,
lineNumbersMinChars: 0,
lineNumbersDigitCount: 3,
maxLineNumber: 120,
lineDecorationsWidth: 10,
typicalHalfwidthCharacterWidth: 10,
maxDigitWidth: 10,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
scrollbarArrowSize: 0,
verticalScrollbarHasArrows: false,
minimap: true,
minimapSide: 'right',
minimapRenderCharacters: true,
minimapMaxColumn: 150,
minimapMode: 'cover',
pixelRatio: 2,
}, {
width: 1000,
height: 800,
glyphMarginLeft: 0,
glyphMarginWidth: 0,
lineNumbersLeft: 0,
lineNumbersWidth: 0,
decorationsLeft: 0,
decorationsWidth: 10,
contentLeft: 10,
contentWidth: 893,
renderMinimap: RenderMinimap.Text,
minimapLeft: 903,
minimapWidth: 97,
minimapHeightIsEditorHeight: true,
minimapIsSampling: false,
minimapScale: 3,
minimapLineHeight: 13,
minimapCanvasInnerWidth: 291,
minimapCanvasInnerHeight: 1560,
minimapCanvasOuterWidth: 97,
minimapCanvasOuterHeight: 800,
viewportColumn: 89,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
overviewRuler: {
top: 0,
width: 0,
height: 800,
right: 0
}
});
});
test('EditorLayoutProvider 12 - minimap mode cover with sampling', () => {
doTest({
outerWidth: 1000,
outerHeight: 800,
showGlyphMargin: false,
lineHeight: 16,
showLineNumbers: false,
lineNumbersMinChars: 0,
lineNumbersDigitCount: 4,
maxLineNumber: 2500,
lineDecorationsWidth: 10,
typicalHalfwidthCharacterWidth: 10,
maxDigitWidth: 10,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
scrollbarArrowSize: 0,
verticalScrollbarHasArrows: false,
minimap: true,
minimapSide: 'right',
minimapRenderCharacters: true,
minimapMaxColumn: 150,
minimapMode: 'cover',
pixelRatio: 2,
}, {
width: 1000,
height: 800,
glyphMarginLeft: 0,
glyphMarginWidth: 0,
lineNumbersLeft: 0,
lineNumbersWidth: 0,
decorationsLeft: 0,
decorationsWidth: 10,
contentLeft: 10,
contentWidth: 935,
renderMinimap: RenderMinimap.Text,
minimapLeft: 945,
minimapWidth: 55,
minimapHeightIsEditorHeight: true,
minimapIsSampling: true,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 110,
minimapCanvasInnerHeight: 1600,
minimapCanvasOuterWidth: 55,
minimapCanvasOuterHeight: 800,
viewportColumn: 93,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
overviewRuler: {
top: 0,
width: 0,
height: 800,
right: 0
}
});
});
test('EditorLayoutProvider 13 - minimap mode contain without sampling', () => {
doTest({
outerWidth: 1000,
outerHeight: 800,
showGlyphMargin: false,
lineHeight: 16,
showLineNumbers: false,
lineNumbersMinChars: 0,
lineNumbersDigitCount: 3,
maxLineNumber: 120,
lineDecorationsWidth: 10,
typicalHalfwidthCharacterWidth: 10,
maxDigitWidth: 10,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
scrollbarArrowSize: 0,
verticalScrollbarHasArrows: false,
minimap: true,
minimapSide: 'right',
minimapRenderCharacters: true,
minimapMaxColumn: 150,
minimapMode: 'contain',
pixelRatio: 2,
}, {
width: 1000,
height: 800,
glyphMarginLeft: 0,
glyphMarginWidth: 0,
lineNumbersLeft: 0,
lineNumbersWidth: 0,
decorationsLeft: 0,
decorationsWidth: 10,
contentLeft: 10,
contentWidth: 893,
renderMinimap: RenderMinimap.Text,
minimapLeft: 903,
minimapWidth: 97,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 2,
minimapLineHeight: 4,
minimapCanvasInnerWidth: 194,
minimapCanvasInnerHeight: 1600,
minimapCanvasOuterWidth: 97,
minimapCanvasOuterHeight: 800,
viewportColumn: 89,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
overviewRuler: {
top: 0,
width: 0,
height: 800,
right: 0
}
});
});
test('EditorLayoutProvider 14 - minimap mode contain with sampling', () => {
doTest({
outerWidth: 1000,
outerHeight: 800,
showGlyphMargin: false,
lineHeight: 16,
showLineNumbers: false,
lineNumbersMinChars: 0,
lineNumbersDigitCount: 4,
maxLineNumber: 2500,
lineDecorationsWidth: 10,
typicalHalfwidthCharacterWidth: 10,
maxDigitWidth: 10,
verticalScrollbarWidth: 0,
horizontalScrollbarHeight: 0,
scrollbarArrowSize: 0,
verticalScrollbarHasArrows: false,
minimap: true,
minimapSide: 'right',
minimapRenderCharacters: true,
minimapMaxColumn: 150,
minimapMode: 'contain',
pixelRatio: 2,
}, {
width: 1000,
height: 800,
glyphMarginLeft: 0,
glyphMarginWidth: 0,
lineNumbersLeft: 0,
lineNumbersWidth: 0,
decorationsLeft: 0,
decorationsWidth: 10,
contentLeft: 10,
contentWidth: 935,
renderMinimap: RenderMinimap.Text,
minimapLeft: 945,
minimapWidth: 55,
minimapHeightIsEditorHeight: true,
minimapIsSampling: true,
minimapScale: 1,
minimapLineHeight: 1,
minimapCanvasInnerWidth: 110,
minimapCanvasInnerHeight: 1600,
minimapCanvasOuterWidth: 55,
minimapCanvasOuterHeight: 800,
viewportColumn: 93,
verticalScrollbarWidth: 0,
@@ -881,6 +1253,14 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
renderMinimap: RenderMinimap.Text,
minimapLeft: 1096,
minimapWidth: 91,
minimapHeightIsEditorHeight: false,
minimapIsSampling: false,
minimapScale: 2,
minimapLineHeight: 4,
minimapCanvasInnerWidth: 182,
minimapCanvasInnerHeight: 844,
minimapCanvasOuterWidth: 91,
minimapCanvasOuterHeight: 422,
viewportColumn: 83,
verticalScrollbarWidth: 14,
+40
View File
@@ -2390,6 +2390,8 @@ declare namespace monaco.editor {
* An event describing that model decorations have changed.
*/
export interface IModelDecorationsChangedEvent {
readonly affectsMinimap: boolean;
readonly affectsOverviewRuler: boolean;
}
export interface IModelOptionsChangedEvent {
@@ -3330,6 +3332,14 @@ declare namespace monaco.editor {
* The width of the minimap
*/
readonly minimapWidth: number;
readonly minimapHeightIsEditorHeight: boolean;
readonly minimapIsSampling: boolean;
readonly minimapScale: number;
readonly minimapLineHeight: number;
readonly minimapCanvasInnerWidth: number;
readonly minimapCanvasInnerHeight: number;
readonly minimapCanvasOuterWidth: number;
readonly minimapCanvasOuterHeight: number;
/**
* Minimap render type
*/
@@ -3379,6 +3389,11 @@ declare namespace monaco.editor {
* Defaults to 'right'.
*/
side?: 'right' | 'left';
/**
* Control the minimap rendering mode.
* Defaults to 'actual'.
*/
mode?: 'actual' | 'cover' | 'contain';
/**
* Control the rendering of the minimap slider.
* Defaults to 'mouseover'.
@@ -5238,6 +5253,31 @@ declare namespace monaco.languages {
provideHover(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<Hover>;
}
/**
* An evaluatable expression represents additional information for an expression in a document. Evaluatable expression are
* evaluated by a debugger or runtime and their result is rendered in a tooltip-like widget.
*/
export interface EvaluatableExpression {
/**
* The range to which this expression applies.
*/
range: IRange;
expression?: string;
}
/**
* The hover provider interface defines the contract between extensions and
* the [hover](https://code.visualstudio.com/docs/editor/intellisense)-feature.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide a hover for the given position and document. Multiple hovers at the same
* position will be merged by the editor. A hover can have a range which defaults
* to the word range at the position when omitted.
*/
provideEvaluatableExpression(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export enum CompletionItemKind {
Method = 0,
Function = 1,
+3 -1
View File
@@ -114,7 +114,9 @@ export class MenuId {
static readonly CommentActions = new MenuId('CommentActions');
static readonly BulkEditTitle = new MenuId('BulkEditTitle');
static readonly BulkEditContext = new MenuId('BulkEditContext');
static readonly TimelineItemContext = new MenuId('TimelineItemContext');
static readonly TimelineTitle = new MenuId('TimelineTitle');
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
readonly id: number;
readonly _debugName: string;
@@ -29,7 +29,7 @@ export interface IConstructorSignature0<T> {
}
export interface IConstructorSignature1<A1, T> {
new(first: A1, ...services: BrandedService[]): T;
new <Services extends BrandedService[]>(first: A1, ...services: Services): T;
}
export interface IConstructorSignature2<A1, A2, T> {
+4 -4
View File
@@ -135,15 +135,15 @@ export namespace IMarkerData {
export function makeKeyOptionalMessage(markerData: IMarkerData, useMessage: boolean): string {
let result: string[] = [emptyString];
if (markerData.source) {
result.push(markerData.source.replace('¦', '\¦'));
result.push(markerData.source.replace('¦', '\\¦'));
} else {
result.push(emptyString);
}
if (markerData.code) {
if (typeof markerData.code === 'string') {
result.push(markerData.code.replace('¦', '\¦'));
result.push(markerData.code.replace('¦', '\\¦'));
} else {
result.push(markerData.code.value.replace('¦', '\¦'));
result.push(markerData.code.value.replace('¦', '\\¦'));
}
} else {
result.push(emptyString);
@@ -157,7 +157,7 @@ export namespace IMarkerData {
// Modifed to not include the message as part of the marker key to work around
// https://github.com/microsoft/vscode/issues/77475
if (markerData.message && useMessage) {
result.push(markerData.message.replace('¦', '\¦'));
result.push(markerData.message.replace('¦', '\\¦'));
} else {
result.push(emptyString);
}
@@ -6,7 +6,7 @@
import BaseSeverity from 'vs/base/common/severity';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IAction } from 'vs/base/common/actions';
import { Event, Emitter } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
export import Severity = BaseSeverity;
@@ -318,16 +318,14 @@ export class NoOpNotification implements INotificationHandle {
readonly progress = new NoOpProgress();
private readonly _onDidClose: Emitter<void> = new Emitter();
readonly onDidClose: Event<void> = this._onDidClose.event;
readonly onDidClose = Event.None;
readonly onDidChangeVisibility = Event.None;
updateSeverity(severity: Severity): void { }
updateMessage(message: NotificationMessage): void { }
updateActions(actions?: INotificationActions): void { }
close(): void {
this._onDidClose.dispose();
}
close(): void { }
}
export class NoOpProgress implements INotificationProgress {
+17 -19
View File
@@ -17,7 +17,11 @@ export interface IProgressService {
_serviceBrand: undefined;
withProgress<R = any>(options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions, task: (progress: IProgress<IProgressStep>) => Promise<R>, onDidCancel?: (choice?: number) => void): Promise<R>;
withProgress<R = any>(
options: IProgressOptions | IProgressNotificationOptions | IProgressWindowOptions | IProgressCompositeOptions,
task: (progress: IProgress<IProgressStep>) => Promise<R>,
onDidCancel?: (choice?: number) => void
): Promise<R>;
}
export interface IProgressIndicator {
@@ -45,19 +49,19 @@ export const enum ProgressLocation {
}
export interface IProgressOptions {
location: ProgressLocation | string;
title?: string;
source?: string;
total?: number;
cancellable?: boolean;
buttons?: string[];
readonly location: ProgressLocation | string;
readonly title?: string;
readonly source?: string;
readonly total?: number;
readonly cancellable?: boolean;
readonly buttons?: string[];
}
export interface IProgressNotificationOptions extends IProgressOptions {
readonly location: ProgressLocation.Notification;
readonly primaryActions?: ReadonlyArray<IAction>;
readonly secondaryActions?: ReadonlyArray<IAction>;
delay?: number;
readonly delay?: number;
}
export interface IProgressWindowOptions extends IProgressOptions {
@@ -66,8 +70,8 @@ export interface IProgressWindowOptions extends IProgressOptions {
}
export interface IProgressCompositeOptions extends IProgressOptions {
location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string;
delay?: number;
readonly location: ProgressLocation.Explorer | ProgressLocation.Extensions | ProgressLocation.Scm | string;
readonly delay?: number;
}
export interface IProgressStep {
@@ -96,20 +100,14 @@ export interface IProgress<T> {
export class Progress<T> implements IProgress<T> {
private _callback: (data: T) => void;
private _value?: T;
get value(): T | undefined { return this._value; }
constructor(callback: (data: T) => void) {
this._callback = callback;
}
get value(): T | undefined {
return this._value;
}
constructor(private callback: (data: T) => void) { }
report(item: T) {
this._value = item;
this._callback(this._value);
this.callback(this._value);
}
}
@@ -98,7 +98,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
async triggerAutoSync(): Promise<void> {
if (this.enabled) {
return this.syncDelayer.trigger(() => {
this.logService.info('Auto Sync: Triggerred.');
this.logService.info('Auto Sync: Triggered.');
return this.sync(false, true);
}, this.successiveFailures
? 1000 * 1 * Math.min(this.successiveFailures, 60) /* Delay by number of seconds as number of failures up to 1 minute */
+96 -3
View File
@@ -876,7 +876,65 @@ declare module 'vscode' {
//#endregion
//#region Debug:
//#region locate evaluatable expressions for debug hover: https://github.com/microsoft/vscode/issues/89084
/**
* An EvaluatableExpression represents an expression in a document that can be evaluated by an active debugger or runtime.
* The result of this evaluation is shown in a tooltip-like widget.
* If only a range is specified, the expression will be extracted from the underlying document.
* An optional expression can be used to override the extracted expression.
* In this case the range is still used to highlight the range in the document.
*/
export class EvaluatableExpression {
/*
* The range is used to extract the evaluatable expression from the underlying document and to highlight it.
*/
readonly range: Range;
/*
* If specified the expression overrides the extracted expression.
*/
readonly expression?: string;
/**
* Creates a new evaluatable expression object.
*
* @param range The range in the underlying document from which the evaluatable expression is extracted.
* @param expression If specified overrides the extracted expression.
*/
constructor(range: Range, expression?: string);
}
/**
* The evaluatable expression provider interface defines the contract between extensions and
* the debug hover.
*/
export interface EvaluatableExpressionProvider {
/**
* Provide an evaluatable expression for the given document and position.
* The expression can be implicitly specified by the range in the underlying document or by explicitly returning an expression.
*
* @param document The document in which the command was invoked.
* @param position The position where the command was invoked.
* @param token A cancellation token.
* @return An EvaluatableExpression or a thenable that resolves to such. The lack of a result can be
* signaled by returning `undefined` or `null`.
*/
provideEvaluatableExpression(document: TextDocument, position: Position, token: CancellationToken): ProviderResult<EvaluatableExpression>;
}
export namespace languages {
/**
* Register a provider that locates evaluatable expressions in text documents.
*
* If multiple providers are registered for a language an arbitrary provider will be used.
*
* @param selector A selector that defines the documents this provider is applicable to.
* @param provider An evaluatable expression provider.
* @return A [disposable](#Disposable) that unregisters this provider when being disposed.
*/
export function registerEvaluatableExpressionProvider(selector: DocumentSelector, provider: EvaluatableExpressionProvider): Disposable;
}
// deprecated
@@ -1553,6 +1611,40 @@ declare module 'vscode' {
uri?: Uri;
}
export interface TimelineCursor {
/**
* A provider-defined cursor specifing the range of timeline items to be returned. Must be serializable.
*/
cursor?: any;
/**
* A flag to specify whether the timeline items requested are before or after (default) the provided cursor.
*/
before?: boolean;
/**
* The maximum number of timeline items that should be returned.
*/
limit?: number;
}
export interface Timeline {
/**
* A provider-defined cursor specifing the range of timeline items returned. Must be serializable.
*/
cursor?: any;
/**
* A flag which indicates whether there are any more items that weren't returned.
*/
more?: boolean;
/**
* An array of [timeline items](#TimelineItem).
*/
items: TimelineItem[];
}
export interface TimelineProvider {
/**
* An optional event to signal that the timeline for a source has changed.
@@ -1575,10 +1667,11 @@ declare module 'vscode' {
*
* @param uri The [uri](#Uri) of the file to provide the timeline for.
* @param token A cancellation token.
* @return An array of timeline items or a thenable that resolves to such. The lack of a result
* @param cursor TBD
* @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result
* can be signaled by returning `undefined`, `null`, or an empty array.
*/
provideTimeline(uri: Uri, token: CancellationToken): ProviderResult<TimelineItem[]>;
provideTimeline(uri: Uri, cursor: TimelineCursor, token: CancellationToken): ProviderResult<Timeline>;
}
export namespace workspace {
@@ -213,6 +213,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}));
}
// --- debug hover
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, modes.EvaluatableExpressionProviderRegistry.register(selector, <modes.EvaluatableExpressionProvider>{
provideEvaluatableExpression: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> => {
return this._proxy.$provideEvaluatableExpression(handle, model.uri, position, token);
}
}));
}
// --- occurrences
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void {
@@ -3,304 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IdleValue, raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
import * as strings from 'vs/base/common/strings';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ITextModel } from 'vs/editor/common/model';
import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress';
import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress';
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ISaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileSaveParticipant, IResolvedTextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol';
import { ILabelService } from 'vs/platform/label/common/label';
import { canceled } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface ISaveParticipantParticipant {
participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void>;
}
class TrimWhitespaceParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void {
let prevSelection: Selection[] = [];
let cursors: Position[] = [];
const editor = findEditor(model, this.codeEditorService);
if (editor) {
// Find `prevSelection` in any case do ensure a good undo stack when pushing the edit
// Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump
prevSelection = editor.getSelections();
if (isAutoSaved) {
cursors = prevSelection.map(s => s.getPosition());
const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange();
if (snippetsRange) {
for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) {
cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber)));
}
}
}
}
const ops = trimTrailingWhitespace(model, cursors);
if (!ops.length) {
return; // Nothing to do
}
model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection);
}
}
function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null {
let candidate: IActiveCodeEditor | null = null;
if (model.isAttachedToEditor()) {
for (const editor of codeEditorService.listCodeEditors()) {
if (editor.hasModel() && editor.getModel() === model) {
if (editor.hasTextFocus()) {
return editor; // favour focused editor if there are multiple
}
candidate = editor;
}
}
}
return candidate;
}
export class FinalNewLineParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doInsertFinalNewLine(model.textEditorModel);
}
}
private doInsertFinalNewLine(model: ITextModel): void {
const lineCount = model.getLineCount();
const lastLine = model.getLineContent(lineCount);
const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1;
if (!lineCount || lastLineIsEmptyOrWhitespace) {
return;
}
const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())];
const editor = findEditor(model, this.codeEditorService);
if (editor) {
editor.executeEdits('insertFinalNewLine', edits, editor.getSelections());
} else {
model.pushEditOperations([], edits, () => null);
}
}
}
export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
/**
* returns 0 if the entire file is empty or whitespace only
*/
private findLastLineWithContent(model: ITextModel): number {
for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) {
const lineContent = model.getLineContent(lineNumber);
if (strings.lastNonWhitespaceIndex(lineContent) !== -1) {
// this line has content
return lineNumber;
}
}
// no line has content
return 0;
}
private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void {
const lineCount = model.getLineCount();
// Do not insert new line if file does not end with new line
if (lineCount === 1) {
return;
}
let prevSelection: Selection[] = [];
let cannotTouchLineNumber = 0;
const editor = findEditor(model, this.codeEditorService);
if (editor) {
prevSelection = editor.getSelections();
if (isAutoSaved) {
for (let i = 0, len = prevSelection.length; i < len; i++) {
const positionLineNumber = prevSelection[i].positionLineNumber;
if (positionLineNumber > cannotTouchLineNumber) {
cannotTouchLineNumber = positionLineNumber;
}
}
}
}
const lastLineNumberWithContent = this.findLastLineWithContent(model);
const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1);
const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount)));
if (deletionRange.isEmpty()) {
return;
}
model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection);
if (editor) {
editor.setSelections(prevSelection);
}
}
}
class FormatOnSaveParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
// Nothing
}
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
const model = editorModel.textEditorModel;
const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri };
if (env.reason === SaveReason.AUTO || !this._configurationService.getValue('editor.formatOnSave', overrides)) {
return undefined;
}
progress.report({ message: localize('formatting', "Formatting") });
const editorOrModel = findEditor(model, this._codeEditorService) || model;
await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token);
}
}
class CodeActionOnSaveParticipant implements ISaveParticipantParticipant {
constructor(
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) { }
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
if (env.reason === SaveReason.AUTO) {
return undefined;
}
const model = editorModel.textEditorModel;
const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource };
const setting = this._configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides);
if (!setting) {
return undefined;
}
const codeActionsOnSave = Object.keys(setting)
.filter(x => setting[x]).map(x => new CodeActionKind(x))
.sort((a, b) => {
if (CodeActionKind.SourceFixAll.contains(a)) {
if (CodeActionKind.SourceFixAll.contains(b)) {
return 0;
}
return -1;
}
if (CodeActionKind.SourceFixAll.contains(b)) {
return 1;
}
return 0;
});
if (!codeActionsOnSave.length) {
return undefined;
}
const excludedActions = Object.keys(setting)
.filter(x => setting[x] === false)
.map(x => new CodeActionKind(x));
progress.report({ message: localize('codeaction', "Quick Fixes") });
await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token);
}
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise<void> {
for (const codeActionKind of codeActionsOnSave) {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
try {
await this.applyCodeActions(actionsToRun.validActions);
} catch {
// Failure to apply a code action should not block other on save actions
} finally {
actionsToRun.dispose();
}
}
}
private async applyCodeActions(actionsToRun: readonly CodeAction[]) {
for (const action of actionsToRun) {
await this._instantiationService.invokeFunction(applyCodeAction, action);
}
}
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) {
return getCodeActions(model, model.getFullModelRange(), {
type: CodeActionTriggerType.Auto,
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
}, token);
}
}
class ExtHostSaveParticipant implements ISaveParticipantParticipant {
class ExtHostSaveParticipant implements ITextFileSaveParticipant {
private readonly _proxy: ExtHostDocumentSaveParticipantShape;
@@ -336,65 +51,19 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant {
// The save participant can change a model before its saved to support various scenarios like trimming trailing whitespace
@extHostCustomer
export class SaveParticipant implements ISaveParticipant {
export class SaveParticipant {
private readonly _saveParticipants: IdleValue<ISaveParticipantParticipant[]>;
private _saveParticipantDisposable: IDisposable;
constructor(
extHostContext: IExtHostContext,
@IInstantiationService instantiationService: IInstantiationService,
@IProgressService private readonly _progressService: IProgressService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
@ITextFileService private readonly _textFileService: ITextFileService
) {
this._saveParticipants = new IdleValue(() => [
instantiationService.createInstance(TrimWhitespaceParticipant),
instantiationService.createInstance(CodeActionOnSaveParticipant),
instantiationService.createInstance(FormatOnSaveParticipant),
instantiationService.createInstance(FinalNewLineParticipant),
instantiationService.createInstance(TrimFinalNewLinesParticipant),
instantiationService.createInstance(ExtHostSaveParticipant, extHostContext),
]);
// Set as save participant for all text files
this._textFileService.saveParticipant = this;
this._saveParticipantDisposable = this._textFileService.files.addSaveParticipant(instantiationService.createInstance(ExtHostSaveParticipant, extHostContext));
}
dispose(): void {
this._textFileService.saveParticipant = undefined;
this._saveParticipants.dispose();
}
async participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise<void> {
const cts = new CancellationTokenSource(token);
return this._progressService.withProgress({
title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })),
location: ProgressLocation.Notification,
cancellable: true,
delay: model.isDirty() ? 3000 : 5000
}, async progress => {
// undoStop before participation
model.textEditorModel.pushStackElement();
for (let p of this._saveParticipants.getValue()) {
if (cts.token.isCancellationRequested) {
break;
}
try {
const promise = p.participate(model, context, progress, cts.token);
await raceCancellation(promise, cts.token);
} catch (err) {
this._logService.warn(err);
}
}
// undoStop after participation
model.textEditorModel.pushStackElement();
}, () => {
// user cancel
cts.dispose(true);
});
this._saveParticipantDisposable.dispose();
}
}
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { MainContext, MainThreadTimelineShape, IExtHostContext, ExtHostTimelineShape, ExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ITimelineService, TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent } from 'vs/workbench/contrib/timeline/common/timeline';
import { TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor, ITimelineService } from 'vs/workbench/contrib/timeline/common/timeline';
@extHostNamedCustomer(MainContext.MainThreadTimeline)
export class MainThreadTimeline implements MainThreadTimelineShape {
@@ -24,10 +24,6 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
this._proxy = context.getProxy(ExtHostContext.ExtHostTimeline);
}
$getTimeline(uri: URI, token: CancellationToken): Promise<TimelineItem[]> {
return this._timelineService.getTimeline(uri, token);
}
$registerTimelineProvider(provider: TimelineProviderDescriptor): void {
this.logService.trace(`MainThreadTimeline#registerTimelineProvider: id=${provider.id}`);
@@ -43,8 +39,8 @@ export class MainThreadTimeline implements MainThreadTimelineShape {
this._timelineService.registerTimelineProvider({
...provider,
onDidChange: onDidChange.event,
provideTimeline(uri: URI, token: CancellationToken) {
return proxy.$getTimeline(provider.id, uri, token);
provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
return proxy.$getTimeline(provider.id, uri, cursor, token, options);
},
dispose() {
emitters.delete(provider.id);
@@ -133,7 +133,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHostLabelService, new ExtHostLabelService(rpcProtocol));
const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol));
const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol));
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol));
const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands));
// Check that no named customers are missing
const expected: ProxyIdentifier<any>[] = values(ExtHostContext);
@@ -345,6 +345,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider): vscode.Disposable {
return extHostLanguageFeatures.registerHoverProvider(extension, checkSelector(selector), provider, extension.identifier);
},
registerEvaluatableExpressionProvider(selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider): vscode.Disposable {
return extHostLanguageFeatures.registerEvaluatableExpressionProvider(extension, checkSelector(selector), provider, extension.identifier);
},
registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
return extHostLanguageFeatures.registerDocumentHighlightProvider(extension, checkSelector(selector), provider);
},
@@ -926,6 +929,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
DocumentLink: extHostTypes.DocumentLink,
DocumentSymbol: extHostTypes.DocumentSymbol,
EndOfLine: extHostTypes.EndOfLine,
EvaluatableExpression: extHostTypes.EvaluatableExpression,
EventEmitter: Emitter,
ExtensionKind: extHostTypes.ExtensionKind,
CustomExecution: extHostTypes.CustomExecution,
@@ -49,7 +49,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { TimelineItem, TimelineProviderDescriptor, TimelineChangeEvent, TimelineItemWithSource } from 'vs/workbench/contrib/timeline/common/timeline';
import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -353,6 +353,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerImplementationSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerTypeDefinitionSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerHoverProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void;
$registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void;
$registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void;
@@ -802,8 +803,6 @@ export interface MainThreadTimelineShape extends IDisposable {
$registerTimelineProvider(provider: TimelineProviderDescriptor): void;
$unregisterTimelineProvider(source: string): void;
$emitTimelineChangeEvent(e: TimelineChangeEvent): void;
$getTimeline(uri: UriComponents, token: CancellationToken): Promise<TimelineItem[]>;
}
// -- extension host
@@ -1208,6 +1207,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<IDefinitionLinkDto[]>;
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<ILocationDto[] | undefined>;
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<ICodeActionListDto | undefined>;
@@ -1457,7 +1457,7 @@ export interface ExtHostTunnelServiceShape {
}
export interface ExtHostTimelineShape {
$getTimeline(source: string, uri: UriComponents, token: CancellationToken): Promise<TimelineItemWithSource[]>;
$getTimeline(source: string, uri: UriComponents, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
// --- proxy identifiers
@@ -276,6 +276,27 @@ class HoverAdapter {
}
}
class EvaluatableExpressionAdapter {
constructor(
private readonly _documents: ExtHostDocuments,
private readonly _provider: vscode.EvaluatableExpressionProvider,
) { }
public provideEvaluatableExpression(resource: URI, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
const doc = this._documents.getDocument(resource);
const pos = typeConvert.Position.to(position);
return asPromise(() => this._provider.provideEvaluatableExpression(doc, pos, token)).then(value => {
if (value) {
return typeConvert.EvaluatableExpression.from(value);
}
return undefined;
});
}
}
class DocumentHighlightAdapter {
constructor(
@@ -1329,7 +1350,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov
| RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter
| SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter
| TypeDefinitionAdapter | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter;
| SelectionRangeAdapter | CallHierarchyAdapter | DocumentSemanticTokensAdapter | DocumentRangeSemanticTokensAdapter | EvaluatableExpressionAdapter;
class AdapterData {
constructor(
@@ -1549,6 +1570,18 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token), undefined);
}
// --- debug hover
registerEvaluatableExpressionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.EvaluatableExpressionProvider, extensionId?: ExtensionIdentifier): vscode.Disposable {
const handle = this._addNewAdapter(new EvaluatableExpressionAdapter(this._documents, provider), extension);
this._proxy.$registerEvaluatableExpressionProvider(handle, this._transformDocumentSelector(selector));
return this._createDisposable(handle);
}
$provideEvaluatableExpression(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.EvaluatableExpression | undefined> {
return this._withAdapter(handle, EvaluatableExpressionAdapter, adapter => adapter.provideEvaluatableExpression(URI.revive(resource), position, token), undefined);
}
// --- occurrences
registerDocumentHighlightProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable {
+106 -34
View File
@@ -7,61 +7,91 @@ import * as vscode from 'vscode';
import { UriComponents, URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ExtHostTimelineShape, MainThreadTimelineShape, IMainContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { TimelineItemWithSource, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
import { Timeline, TimelineCursor, TimelineItem, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ThemeIcon } from 'vs/workbench/api/common/extHostTypes';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export interface IExtHostTimeline extends ExtHostTimelineShape {
readonly _serviceBrand: undefined;
$getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]>;
$getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined>;
}
export const IExtHostTimeline = createDecorator<IExtHostTimeline>('IExtHostTimeline');
export class ExtHostTimeline implements IExtHostTimeline {
private static handlePool = 0;
_serviceBrand: undefined;
private _proxy: MainThreadTimelineShape;
private _providers = new Map<string, TimelineProvider>();
private _itemsBySourceByUriMap = new Map<string | undefined, Map<string, Map<string, vscode.TimelineItem>>>();
constructor(
mainContext: IMainContext,
commands: ExtHostCommands,
) {
this._proxy = mainContext.getProxy(MainContext.MainThreadTimeline);
commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.$mid === 11) {
const uri = arg.uri === undefined ? undefined : URI.revive(arg.uri);
return this._itemsBySourceByUriMap.get(getUriKey(uri))?.get(arg.source)?.get(arg.handle);
}
return arg;
}
});
}
async $getTimeline(id: string, uri: UriComponents, token: vscode.CancellationToken): Promise<TimelineItemWithSource[]> {
async $getTimeline(id: string, uri: UriComponents, cursor: vscode.TimelineCursor, token: vscode.CancellationToken, options?: { cacheResults?: boolean }): Promise<Timeline | undefined> {
const provider = this._providers.get(id);
return provider?.provideTimeline(URI.revive(uri), token) ?? [];
return provider?.provideTimeline(URI.revive(uri), cursor, token, options);
}
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
registerTimelineProvider(scheme: string | string[], provider: vscode.TimelineProvider, _extensionId: ExtensionIdentifier, commandConverter: CommandsConverter): IDisposable {
const timelineDisposables = new DisposableStore();
const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables);
const convertTimelineItem = this.convertTimelineItem(provider.id, commandConverter, timelineDisposables).bind(this);
let disposable: IDisposable | undefined;
if (provider.onDidChange) {
disposable = provider.onDidChange(this.emitTimelineChangeEvent(provider.id), this);
}
const itemsBySourceByUriMap = this._itemsBySourceByUriMap;
return this.registerTimelineProviderCore({
...provider,
scheme: scheme,
onDidChange: undefined,
async provideTimeline(uri: URI, token: CancellationToken) {
async provideTimeline(uri: URI, cursor: TimelineCursor, token: CancellationToken, options?: { cacheResults?: boolean }) {
timelineDisposables.clear();
const results = await provider.provideTimeline(uri, token);
// For now, only allow the caching of a single Uri
if (options?.cacheResults && !itemsBySourceByUriMap.has(getUriKey(uri))) {
itemsBySourceByUriMap.clear();
}
const result = await provider.provideTimeline(uri, cursor, token);
// Intentional == we don't know how a provider will respond
// eslint-disable-next-line eqeqeq
return results != null
? results.map(item => convertTimelineItem(item))
: [];
if (result == null) {
return undefined;
}
// TODO: Determine if we should cache dependent on who calls us (internal vs external)
const convertItem = convertTimelineItem(uri, options?.cacheResults ?? false);
return {
...result,
source: provider.id,
items: result.items.map(convertItem)
};
},
dispose() {
disposable?.dispose();
@@ -70,39 +100,72 @@ export class ExtHostTimeline implements IExtHostTimeline {
});
}
private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore): (item: vscode.TimelineItem) => TimelineItemWithSource {
return (item: vscode.TimelineItem) => {
const { iconPath, ...props } = item;
private convertTimelineItem(source: string, commandConverter: CommandsConverter, disposables: DisposableStore) {
return (uri: URI, cacheResults: boolean) => {
let itemsMap: Map<string, vscode.TimelineItem> | undefined;
if (cacheResults) {
const uriKey = getUriKey(uri);
let icon;
let iconDark;
let themeIcon;
if (item.iconPath) {
if (iconPath instanceof ThemeIcon) {
themeIcon = { id: iconPath.id };
let sourceMap = this._itemsBySourceByUriMap.get(uriKey);
if (sourceMap === undefined) {
sourceMap = new Map();
this._itemsBySourceByUriMap.set(uriKey, sourceMap);
}
else if (URI.isUri(iconPath)) {
icon = iconPath;
iconDark = iconPath;
}
else {
({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI });
itemsMap = sourceMap.get(source);
if (itemsMap === undefined) {
itemsMap = new Map();
sourceMap.set(source, itemsMap);
}
}
return {
...props,
source: source,
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
icon: icon,
iconDark: iconDark,
themeIcon: themeIcon
return (item: vscode.TimelineItem): TimelineItem => {
const { iconPath, ...props } = item;
const handle = `${source}|${item.id ?? `${item.timestamp}-${ExtHostTimeline.handlePool++}`}`;
itemsMap?.set(handle, item);
let icon;
let iconDark;
let themeIcon;
if (item.iconPath) {
if (iconPath instanceof ThemeIcon) {
themeIcon = { id: iconPath.id };
}
else if (URI.isUri(iconPath)) {
icon = iconPath;
iconDark = iconPath;
}
else {
({ light: icon, dark: iconDark } = iconPath as { light: URI; dark: URI });
}
}
return {
...props,
handle: handle,
source: source,
command: item.command ? commandConverter.toInternal(item.command, disposables) : undefined,
icon: icon,
iconDark: iconDark,
themeIcon: themeIcon
};
};
};
}
private emitTimelineChangeEvent(id: string) {
return (e: vscode.TimelineChangeEvent) => {
// Clear caches
if (e?.uri === undefined) {
for (const sourceMap of this._itemsBySourceByUriMap.values()) {
sourceMap.get(id)?.clear();
}
}
else {
this._itemsBySourceByUriMap.get(getUriKey(e.uri))?.clear();
}
this._proxy.$emitTimelineChangeEvent({ ...e, id: id });
};
}
@@ -123,9 +186,18 @@ export class ExtHostTimeline implements IExtHostTimeline {
this._providers.set(provider.id, provider);
return toDisposable(() => {
for (const sourceMap of this._itemsBySourceByUriMap.values()) {
sourceMap.get(provider.id)?.clear();
}
this._providers.delete(provider.id);
this._proxy.$unregisterTimelineProvider(provider.id);
provider.dispose();
});
}
}
function getUriKey(uri: URI | undefined): string | undefined {
return uri?.toString();
}
@@ -257,7 +257,7 @@ export namespace MarkdownString {
} else if (htmlContent.isMarkdownString(markup)) {
res = markup;
} else if (typeof markup === 'string') {
res = { value: <string>markup };
res = { value: markup };
} else {
res = { value: '' };
}
@@ -737,6 +737,20 @@ export namespace Hover {
return new types.Hover(info.contents.map(MarkdownString.to), Range.to(info.range));
}
}
export namespace EvaluatableExpression {
export function from(expression: vscode.EvaluatableExpression): modes.EvaluatableExpression {
return <modes.EvaluatableExpression>{
range: Range.from(expression.range),
expression: expression.expression
};
}
export function to(info: modes.EvaluatableExpression): types.EvaluatableExpression {
return new types.EvaluatableExpression(Range.to(info.range), info.expression);
}
}
export namespace DocumentHighlight {
export function from(documentHighlight: vscode.DocumentHighlight): modes.DocumentHighlight {
return {
@@ -2283,6 +2283,17 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
}
}
@es5ClassCompat
export class EvaluatableExpression implements vscode.EvaluatableExpression {
readonly range: vscode.Range;
readonly expression?: string;
constructor(range: vscode.Range, expression?: string) {
this.range = range;
this.expression = expression;
}
}
export enum LogLevel {
Trace = 1,
Debug = 2,
@@ -52,6 +52,8 @@ namespace schema {
case 'comments/comment/title': return MenuId.CommentTitle;
case 'comments/comment/context': return MenuId.CommentActions;
case 'extension/context': return MenuId.ExtensionContext;
case 'timeline/title': return MenuId.TimelineTitle;
case 'timeline/item/context': return MenuId.TimelineItemContext;
}
return undefined;
@@ -215,6 +217,16 @@ namespace schema {
type: 'array',
items: menuItem
},
'timeline/title': {
description: localize('view.timelineTitle', "The Timeline view title menu"),
type: 'array',
items: menuItem
},
'timeline/item/context': {
description: localize('view.timelineContext', "The Timeline view item context menu"),
type: 'array',
items: menuItem
},
}
};
+1 -1
View File
@@ -239,7 +239,7 @@ enum Redraw {
class ResourceLabelWidget extends IconLabel {
private _onDidRender = this._register(new Emitter<void>());
readonly onDidRender: Event<void> = this._onDidRender.event;
readonly onDidRender = this._onDidRender.event;
private readonly renderDisposables = this._register(new DisposableStore());
+14 -14
View File
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, Dimension, toggleClass, position, size } from 'vs/base/browser/dom';
import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
@@ -87,26 +87,26 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
//#region Events
private readonly _onZenModeChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onZenModeChange: Event<boolean> = this._onZenModeChange.event;
private readonly _onZenModeChange = this._register(new Emitter<boolean>());
readonly onZenModeChange = this._onZenModeChange.event;
private readonly _onFullscreenChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onFullscreenChange: Event<boolean> = this._onFullscreenChange.event;
private readonly _onFullscreenChange = this._register(new Emitter<boolean>());
readonly onFullscreenChange = this._onFullscreenChange.event;
private readonly _onCenteredLayoutChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onCenteredLayoutChange: Event<boolean> = this._onCenteredLayoutChange.event;
private readonly _onCenteredLayoutChange = this._register(new Emitter<boolean>());
readonly onCenteredLayoutChange = this._onCenteredLayoutChange.event;
private readonly _onMaximizeChange: Emitter<boolean> = this._register(new Emitter<boolean>());
readonly onMaximizeChange: Event<boolean> = this._onMaximizeChange.event;
private readonly _onMaximizeChange = this._register(new Emitter<boolean>());
readonly onMaximizeChange = this._onMaximizeChange.event;
private readonly _onPanelPositionChange: Emitter<string> = this._register(new Emitter<string>());
readonly onPanelPositionChange: Event<string> = this._onPanelPositionChange.event;
private readonly _onPanelPositionChange = this._register(new Emitter<string>());
readonly onPanelPositionChange = this._onPanelPositionChange.event;
private readonly _onPartVisibilityChange: Emitter<void> = this._register(new Emitter<void>());
readonly onPartVisibilityChange: Event<void> = this._onPartVisibilityChange.event;
private readonly _onPartVisibilityChange = this._register(new Emitter<void>());
readonly onPartVisibilityChange = this._onPartVisibilityChange.event;
private readonly _onLayout = this._register(new Emitter<IDimension>());
readonly onLayout: Event<IDimension> = this._onLayout.event;
readonly onLayout = this._onLayout.event;
//#endregion
+1 -1
View File
@@ -34,7 +34,7 @@ export abstract class Part extends Component implements ISerializableView {
get dimension(): Dimension | undefined { return this._dimension; }
protected _onDidVisibilityChange = this._register(new Emitter<boolean>());
readonly onDidVisibilityChange: Event<boolean> = this._onDidVisibilityChange.event;
readonly onDidVisibilityChange = this._onDidVisibilityChange.event;
private parent: HTMLElement | undefined;
private titleArea: HTMLElement | undefined;
@@ -19,7 +19,7 @@ import { Widget } from 'vs/base/browser/ui/widget';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { Emitter, Event } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
export interface ICompositeBarItem {
id: string;
@@ -61,7 +61,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
readonly onDidChange = this._onDidChange.event;
constructor(
items: ICompositeBarItem[],
@@ -39,7 +39,7 @@ export abstract class BaseEditor extends Panel implements IEditor {
readonly minimumHeight = DEFAULT_EDITOR_MIN_DIMENSIONS.height;
readonly maximumHeight = DEFAULT_EDITOR_MAX_DIMENSIONS.height;
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = Event.None;
readonly onDidSizeConstraintsChange = Event.None;
protected _input: EditorInput | undefined;
protected _options: EditorOptions | undefined;
@@ -1580,7 +1580,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
get maximumHeight(): number { return this.editorControl.maximumHeight; }
private _onDidChange = this._register(new Relay<{ width: number; height: number; } | undefined>());
readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event;
readonly onDidChange = this._onDidChange.event;
layout(width: number, height: number): void {
this.dimension = new Dimension(width, height);
@@ -23,10 +23,10 @@ export class NotificationsAlerts extends Disposable {
}
private registerListeners(): void {
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
private onDidChangeNotification(e: INotificationChangeEvent): void {
if (e.kind === NotificationChangeType.ADD) {
// ARIA alert for screen readers
@@ -46,7 +46,7 @@ export class NotificationsAlerts extends Disposable {
private triggerAriaAlert(notifiation: INotificationViewItem): void {
// Trigger the alert again whenever the label changes
const listener = notifiation.onDidLabelChange(e => {
const listener = notifiation.onDidChangeLabel(e => {
if (e.kind === NotificationViewItemLabelKind.MESSAGE) {
this.doTriggerAriaAlert(notifiation);
}
@@ -69,4 +69,4 @@ export class NotificationsAlerts extends Disposable {
alert(alertText);
}
}
}
@@ -58,7 +58,7 @@ export class NotificationsCenter extends Themable {
}
private registerListeners(): void {
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
this._register(this.layoutService.onLayout(dimension => this.layout(dimension)));
}
@@ -167,7 +167,7 @@ export class NotificationsCenter extends Themable {
return keybinding ? keybinding.getLabel() : null;
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
private onDidChangeNotification(e: INotificationChangeEvent): void {
if (!this._isVisible) {
return; // only if visible
}
@@ -181,7 +181,7 @@ export class NotificationsList extends Themable {
}
// Restore DOM focus if we had focus before
if (listHasDOMFocus) {
if (this.isVisible && listHasDOMFocus) {
list.domFocus();
}
}
@@ -34,11 +34,11 @@ export class NotificationsStatus extends Disposable {
}
private registerListeners(): void {
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidStatusMessageChange(e => this.onDidStatusMessageChange(e)));
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
this._register(this.model.onDidChangeStatusMessage(e => this.onDidChangeStatusMessage(e)));
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
private onDidChangeNotification(e: INotificationChangeEvent): void {
if (this.isNotificationsCenterVisible) {
return; // no change if notification center is visible
}
@@ -101,7 +101,7 @@ export class NotificationsStatus extends Disposable {
}
}
private onDidStatusMessageChange(e: IStatusMessageChangeEvent): void {
private onDidChangeStatusMessage(e: IStatusMessageChangeEvent): void {
const statusItem = e.item;
switch (e.kind) {
@@ -90,11 +90,11 @@ export class NotificationsToasts extends Themable {
this.model.notifications.forEach(notification => this.addToast(notification));
// Update toasts on notification changes
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
this._register(this.model.onDidChangeNotification(e => this.onDidChangeNotification(e)));
});
// Filter
this._register(this.model.onDidFilterChange(filter => {
this._register(this.model.onDidChangeFilter(filter => {
if (filter === NotificationsFilter.SILENT || filter === NotificationsFilter.ERROR) {
this.hide();
}
@@ -114,7 +114,7 @@ export class NotificationsToasts extends Themable {
]);
}
private onDidNotificationChange(e: INotificationChangeEvent): void {
private onDidChangeNotification(e: INotificationChangeEvent): void {
switch (e.kind) {
case NotificationChangeType.ADD:
return this.addToast(e.item);
@@ -194,12 +194,12 @@ export class NotificationsToasts extends Themable {
this.layoutContainer(maxDimensions.height);
// Update when item height changes due to expansion
itemDisposables.add(item.onDidExpansionChange(() => {
itemDisposables.add(item.onDidChangeExpansion(() => {
notificationList.updateNotificationsList(0, 1, [item]);
}));
// Update when item height potentially changes due to label changes
itemDisposables.add(item.onDidLabelChange(e => {
itemDisposables.add(item.onDidChangeLabel(e => {
if (!item.expanded) {
return; // dynamic height only applies to expanded notifications
}
@@ -345,7 +345,7 @@ export class NotificationTemplateRenderer extends Disposable {
this.renderProgress(notification);
// Label Change Events
this.inputDisposables.add(notification.onDidLabelChange(event => {
this.inputDisposables.add(notification.onDidChangeLabel(event => {
switch (event.kind) {
case NotificationViewItemLabelKind.SEVERITY:
this.renderSeverity(notification);
@@ -56,7 +56,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
//#region IView
readonly minimumWidth: number = 420;
readonly minimumWidth: number = 300;
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
readonly minimumHeight: number = 77;
readonly maximumHeight: number = Number.POSITIVE_INFINITY;
@@ -76,7 +76,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
//#endregion
get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean; }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); }
readonly onDidPanelClose: Event<IPanel> = this.onDidCompositeClose.event;
readonly onDidPanelClose = this.onDidCompositeClose.event;
private activePanelContextKey: IContextKey<string>;
private panelFocusContextKey: IContextKey<boolean>;
@@ -71,7 +71,7 @@
display: none;
}
.monaco-workbench .pane > .pane-body > .empty-view {
.monaco-workbench .pane > .pane-body > .welcome-view {
width: 100%;
height: 100%;
padding: 0 20px 0 20px;
@@ -79,12 +79,12 @@
box-sizing: border-box;
}
.monaco-workbench .pane > .pane-body:not(.empty) > .empty-view,
.monaco-workbench .pane > .pane-body.empty > :not(.empty-view) {
.monaco-workbench .pane > .pane-body:not(.welcome) > .welcome-view,
.monaco-workbench .pane > .pane-body.welcome > :not(.welcome-view) {
display: none;
}
.monaco-workbench .pane > .pane-body > .empty-view .monaco-button {
.monaco-workbench .pane > .pane-body > .welcome-view .monaco-button {
max-width: 260px;
margin-left: auto;
margin-right: auto;
@@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry } from 'vs/workbench/common/views';
import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer, IViewsRegistry, IViewContentDescriptor } from 'vs/workbench/common/views';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { assertIsDefined } from 'vs/base/common/types';
@@ -38,7 +38,7 @@ import { Component } from 'vs/workbench/common/component';
import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { parseLinkedText } from 'vs/base/browser/linkedText';
import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
@@ -60,6 +60,88 @@ export interface IViewPaneOptions extends IPaneOptions {
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
interface IItem {
readonly descriptor: IViewContentDescriptor;
visible: boolean;
}
class ViewWelcomeController {
private _onDidChange = new Emitter<void>();
readonly onDidChange = this._onDidChange.event;
private defaultItem: IItem | undefined;
private items: IItem[] = [];
get contents(): IViewContentDescriptor[] {
const visibleItems = this.items.filter(v => v.visible);
if (visibleItems.length === 0 && this.defaultItem) {
return [this.defaultItem.descriptor];
}
return visibleItems.map(v => v.descriptor);
}
private contextKeyService: IContextKeyService;
private disposables = new DisposableStore();
constructor(
private id: string,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this.contextKeyService = contextKeyService.createScoped();
this.disposables.add(this.contextKeyService);
contextKeyService.onDidChangeContext(this.onDidChangeContext, this, this.disposables);
Event.filter(viewsRegistry.onDidChangeViewWelcomeContent, id => id === this.id)(this.onDidChangeViewWelcomeContent, this, this.disposables);
this.onDidChangeViewWelcomeContent();
}
private onDidChangeViewWelcomeContent(): void {
const descriptors = viewsRegistry.getViewWelcomeContent(this.id);
this.items = [];
for (const descriptor of descriptors) {
if (descriptor.when === 'default') {
this.defaultItem = { descriptor, visible: true };
} else {
const visible = descriptor.when ? this.contextKeyService.contextMatchesRules(descriptor.when) : true;
this.items.push({ descriptor, visible });
}
}
this._onDidChange.fire();
}
private onDidChangeContext(): void {
let didChange = false;
for (const item of this.items) {
if (!item.descriptor.when || item.descriptor.when === 'default') {
continue;
}
const visible = this.contextKeyService.contextMatchesRules(item.descriptor.when);
if (item.visible === visible) {
continue;
}
item.visible = visible;
didChange = true;
}
if (didChange) {
this._onDidChange.fire();
}
}
dispose(): void {
this.disposables.dispose();
}
}
export abstract class ViewPane extends Pane implements IView {
private static readonly AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
@@ -76,8 +158,8 @@ export abstract class ViewPane extends Pane implements IView {
protected _onDidChangeTitleArea = this._register(new Emitter<void>());
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
protected _onDidChangeEmptyState = this._register(new Emitter<void>());
readonly onDidChangeEmptyState: Event<void> = this._onDidChangeEmptyState.event;
protected _onDidChangeViewWelcomeState = this._register(new Emitter<void>());
readonly onDidChangeViewWelcomeState: Event<void> = this._onDidChangeViewWelcomeState.event;
private focusedViewContextKey: IContextKey<string>;
@@ -95,8 +177,9 @@ export abstract class ViewPane extends Pane implements IView {
protected twistiesContainer?: HTMLElement;
private bodyContainer!: HTMLElement;
private emptyViewContainer!: HTMLElement;
private emptyViewDisposable: IDisposable = Disposable.None;
private viewWelcomeContainer!: HTMLElement;
private viewWelcomeDisposable: IDisposable = Disposable.None;
private viewWelcomeController: ViewWelcomeController;
constructor(
options: IViewPaneOptions,
@@ -119,6 +202,8 @@ export abstract class ViewPane extends Pane implements IView {
this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext));
this._register(this.menuActions.onDidChangeTitle(() => this.updateActions()));
this.viewWelcomeController = new ViewWelcomeController(this.id, contextKeyService);
}
setVisible(visible: boolean): void {
@@ -206,18 +291,15 @@ export abstract class ViewPane extends Pane implements IView {
protected renderBody(container: HTMLElement): void {
this.bodyContainer = container;
this.emptyViewContainer = append(container, $('.empty-view', { tabIndex: 0 }));
this.viewWelcomeContainer = append(container, $('.welcome-view', { tabIndex: 0 }));
// we should update our empty state whenever
const onEmptyViewContentChange = Event.any(
// the registry changes
Event.map(Event.filter(viewsRegistry.onDidChangeEmptyViewContent, id => id === this.id), () => this.isEmpty()),
// or the view's empty state changes
Event.latch(Event.map(this.onDidChangeEmptyState, () => this.isEmpty()))
);
const onViewWelcomeChange = Event.any(this.viewWelcomeController.onDidChange, this.onDidChangeViewWelcomeState);
this._register(onViewWelcomeChange(this.updateViewWelcome, this));
this.updateViewWelcome();
}
this._register(onEmptyViewContentChange(this.updateEmptyState, this));
this.updateEmptyState(this.isEmpty());
protected layoutBody(height: number, width: number): void {
// noop
}
protected getProgressLocation(): string {
@@ -286,26 +368,26 @@ export abstract class ViewPane extends Pane implements IView {
// Subclasses to implement for saving state
}
private updateEmptyState(isEmpty: boolean): void {
this.emptyViewDisposable.dispose();
private updateViewWelcome(): void {
this.viewWelcomeDisposable.dispose();
if (!isEmpty) {
removeClass(this.bodyContainer, 'empty');
this.emptyViewContainer.innerHTML = '';
if (!this.shouldShowWelcome()) {
removeClass(this.bodyContainer, 'welcome');
this.viewWelcomeContainer.innerHTML = '';
return;
}
const contents = viewsRegistry.getEmptyViewContent(this.id);
const contents = this.viewWelcomeController.contents;
if (contents.length === 0) {
removeClass(this.bodyContainer, 'empty');
this.emptyViewContainer.innerHTML = '';
removeClass(this.bodyContainer, 'welcome');
this.viewWelcomeContainer.innerHTML = '';
return;
}
const disposables = new DisposableStore();
addClass(this.bodyContainer, 'empty');
this.emptyViewContainer.innerHTML = '';
addClass(this.bodyContainer, 'welcome');
this.viewWelcomeContainer.innerHTML = '';
for (const { content } of contents) {
const lines = content.split('\n');
@@ -317,13 +399,13 @@ export abstract class ViewPane extends Pane implements IView {
continue;
}
const p = append(this.emptyViewContainer, $('p'));
const p = append(this.viewWelcomeContainer, $('p'));
const linkedText = parseLinkedText(line);
for (const node of linkedText) {
for (const node of linkedText.nodes) {
if (typeof node === 'string') {
append(p, document.createTextNode(node));
} else if (linkedText.length === 1) {
} else if (linkedText.nodes.length === 1) {
const button = new Button(p, { title: node.title });
button.label = node.label;
button.onDidClick(_ => this.openerService.open(node.href), null, disposables);
@@ -339,10 +421,10 @@ export abstract class ViewPane extends Pane implements IView {
}
}
this.emptyViewDisposable = disposables;
this.viewWelcomeDisposable = disposables;
}
isEmpty(): boolean {
shouldShowWelcome(): boolean {
return false;
}
}
+1 -1
View File
@@ -68,7 +68,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet {
*/
export class ViewletDescriptor extends CompositeDescriptor<Viewlet> {
public static create<Services extends BrandedService[]>(
static create<Services extends BrandedService[]>(
ctor: { new(...services: Services): Viewlet },
id: string,
name: string,
+4 -4
View File
@@ -6,7 +6,7 @@
import 'vs/workbench/browser/style';
import { localize } from 'vs/nls';
import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event';
import { Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event';
import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom';
import { runWhenIdle } from 'vs/base/common/async';
import { getZoomLevel, isFirefox, isSafari, isChrome } from 'vs/base/browser/browser';
@@ -50,13 +50,13 @@ import { Extensions as PanelExtensions, PanelRegistry } from 'vs/workbench/brows
export class Workbench extends Layout {
private readonly _onBeforeShutdown = this._register(new Emitter<BeforeShutdownEvent>());
readonly onBeforeShutdown: Event<BeforeShutdownEvent> = this._onBeforeShutdown.event;
readonly onBeforeShutdown = this._onBeforeShutdown.event;
private readonly _onWillShutdown = this._register(new Emitter<WillShutdownEvent>());
readonly onWillShutdown: Event<WillShutdownEvent> = this._onWillShutdown.event;
readonly onWillShutdown = this._onWillShutdown.event;
private readonly _onShutdown = this._register(new Emitter<void>());
readonly onShutdown: Event<void> = this._onShutdown.event;
readonly onShutdown = this._onShutdown.event;
constructor(
parent: HTMLElement,
+55 -56
View File
@@ -16,28 +16,29 @@ import { find, equals } from 'vs/base/common/arrays';
export interface INotificationsModel {
//
// Notifications as Toasts/Center
//
//#region Notifications as Toasts/Center
readonly notifications: INotificationViewItem[];
readonly onDidNotificationChange: Event<INotificationChangeEvent>;
readonly onDidFilterChange: Event<NotificationsFilter>;
readonly onDidChangeNotification: Event<INotificationChangeEvent>;
readonly onDidChangeFilter: Event<NotificationsFilter>;
addNotification(notification: INotification): INotificationHandle;
setFilter(filter: NotificationsFilter): void;
//
// Notifications as Status
//
//#endregion
//#region Notifications as Status
readonly statusMessage: IStatusMessageViewItem | undefined;
readonly onDidStatusMessageChange: Event<IStatusMessageChangeEvent>;
readonly onDidChangeStatusMessage: Event<IStatusMessageChangeEvent>;
showStatusMessage(message: NotificationMessage, options?: IStatusMessageOptions): IDisposable;
//#endregion
}
export const enum NotificationChangeType {
@@ -87,19 +88,22 @@ export interface IStatusMessageChangeEvent {
kind: StatusMessageChangeType;
}
export class NotificationHandle implements INotificationHandle {
export class NotificationHandle extends Disposable implements INotificationHandle {
private readonly _onDidClose: Emitter<void> = new Emitter();
readonly onDidClose: Event<void> = this._onDidClose.event;
private readonly _onDidClose = this._register(new Emitter<void>());
readonly onDidClose = this._onDidClose.event;
constructor(private readonly item: INotificationViewItem, private readonly onClose: (item: INotificationViewItem) => void) {
super();
constructor(private readonly item: INotificationViewItem, private readonly closeItem: (item: INotificationViewItem) => void) {
this.registerListeners();
}
private registerListeners(): void {
Event.once(this.item.onDidClose)(() => {
this._onDidClose.fire();
this._onDidClose.dispose();
this.dispose();
});
}
@@ -120,8 +124,9 @@ export class NotificationHandle implements INotificationHandle {
}
close(): void {
this.closeItem(this.item);
this._onDidClose.dispose();
this.onClose(this.item);
this.dispose();
}
}
@@ -129,14 +134,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode
private static readonly NO_OP_NOTIFICATION = new NoOpNotification();
private readonly _onDidNotificationChange = this._register(new Emitter<INotificationChangeEvent>());
readonly onDidNotificationChange: Event<INotificationChangeEvent> = this._onDidNotificationChange.event;
private readonly _onDidChangeNotification = this._register(new Emitter<INotificationChangeEvent>());
readonly onDidChangeNotification = this._onDidChangeNotification.event;
private readonly _onDidStatusMessageChange = this._register(new Emitter<IStatusMessageChangeEvent>());
readonly onDidStatusMessageChange: Event<IStatusMessageChangeEvent> = this._onDidStatusMessageChange.event;
private readonly _onDidChangeStatusMessage = this._register(new Emitter<IStatusMessageChangeEvent>());
readonly onDidChangeStatusMessage = this._onDidChangeStatusMessage.event;
private readonly _onDidFilterChange = this._register(new Emitter<NotificationsFilter>());
readonly onDidFilterChange: Event<NotificationsFilter> = this._onDidFilterChange.event;
private readonly _onDidChangeFilter = this._register(new Emitter<NotificationsFilter>());
readonly onDidChangeFilter = this._onDidChangeFilter.event;
private readonly _notifications: INotificationViewItem[] = [];
get notifications(): INotificationViewItem[] { return this._notifications; }
@@ -149,7 +154,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode
setFilter(filter: NotificationsFilter): void {
this.filter = filter;
this._onDidFilterChange.fire(filter);
this._onDidChangeFilter.fire(filter);
}
addNotification(notification: INotification): INotificationHandle {
@@ -168,13 +173,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode
this._notifications.splice(0, 0, item);
// Events
this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD });
this._onDidChangeNotification.fire({ item, index: 0, kind: NotificationChangeType.ADD });
// Wrap into handle
return new NotificationHandle(item, item => this.closeItem(item));
return new NotificationHandle(item, item => this.onClose(item));
}
private closeItem(item: INotificationViewItem): void {
private onClose(item: INotificationViewItem): void {
const liveItem = this.findNotification(item);
if (liveItem && liveItem !== item) {
liveItem.close(); // item could have been replaced with another one, make sure to close the live item
@@ -197,13 +202,13 @@ export class NotificationsModel extends Disposable implements INotificationsMode
const onItemChangeEvent = () => {
const index = this._notifications.indexOf(item);
if (index >= 0) {
this._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.CHANGE });
this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.CHANGE });
}
};
const itemExpansionChangeListener = item.onDidExpansionChange(() => onItemChangeEvent());
const itemExpansionChangeListener = item.onDidChangeExpansion(() => onItemChangeEvent());
const itemLabelChangeListener = item.onDidLabelChange(e => {
const itemLabelChangeListener = item.onDidChangeLabel(e => {
// a label change in the area of actions or the message is a change that potentially has an impact
// on the size of the notification and as such we emit a change event so that viewers can redraw
if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) {
@@ -218,7 +223,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode
const index = this._notifications.indexOf(item);
if (index >= 0) {
this._notifications.splice(index, 1);
this._onDidNotificationChange.fire({ item, index, kind: NotificationChangeType.REMOVE });
this._onDidChangeNotification.fire({ item, index, kind: NotificationChangeType.REMOVE });
}
});
@@ -233,14 +238,14 @@ export class NotificationsModel extends Disposable implements INotificationsMode
// Remember as current status message and fire events
this._statusMessage = item;
this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.ADD, item });
this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.ADD, item });
return toDisposable(() => {
// Only reset status message if the item is still the one we had remembered
if (this._statusMessage === item) {
this._statusMessage = undefined;
this._onDidStatusMessageChange.fire({ kind: StatusMessageChangeType.REMOVE, item });
this._onDidChangeStatusMessage.fire({ kind: StatusMessageChangeType.REMOVE, item });
}
});
}
@@ -258,9 +263,9 @@ export interface INotificationViewItem {
readonly expanded: boolean;
readonly canCollapse: boolean;
readonly onDidExpansionChange: Event<void>;
readonly onDidChangeExpansion: Event<void>;
readonly onDidClose: Event<void>;
readonly onDidLabelChange: Event<INotificationViewItemLabelChangeEvent>;
readonly onDidChangeLabel: Event<INotificationViewItemLabelChangeEvent>;
expand(): void;
collapse(skipEvents?: boolean): void;
@@ -309,8 +314,8 @@ export interface INotificationViewItemProgress extends INotificationProgress {
export class NotificationViewItemProgress extends Disposable implements INotificationViewItemProgress {
private readonly _state: INotificationViewItemProgressState;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
constructor() {
super();
@@ -405,14 +410,14 @@ export class NotificationViewItem extends Disposable implements INotificationVie
private _actions: INotificationActions | undefined;
private _progress: NotificationViewItemProgress | undefined;
private readonly _onDidExpansionChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidExpansionChange: Event<void> = this._onDidExpansionChange.event;
private readonly _onDidChangeExpansion = this._register(new Emitter<void>());
readonly onDidChangeExpansion = this._onDidChangeExpansion.event;
private readonly _onDidClose: Emitter<void> = this._register(new Emitter<void>());
readonly onDidClose: Event<void> = this._onDidClose.event;
private readonly _onDidClose = this._register(new Emitter<void>());
readonly onDidClose = this._onDidClose.event;
private readonly _onDidLabelChange: Emitter<INotificationViewItemLabelChangeEvent> = this._register(new Emitter<INotificationViewItemLabelChangeEvent>());
readonly onDidLabelChange: Event<INotificationViewItemLabelChangeEvent> = this._onDidLabelChange.event;
private readonly _onDidChangeLabel = this._register(new Emitter<INotificationViewItemLabelChangeEvent>());
readonly onDidChangeLabel = this._onDidChangeLabel.event;
static create(notification: INotification, filter: NotificationsFilter = NotificationsFilter.OFF): INotificationViewItem | undefined {
if (!notification || !notification.message || isPromiseCanceledError(notification.message)) {
@@ -561,7 +566,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
get progress(): INotificationViewItemProgress {
if (!this._progress) {
this._progress = this._register(new NotificationViewItemProgress());
this._register(this._progress.onDidChange(() => this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.PROGRESS })));
this._register(this._progress.onDidChange(() => this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.PROGRESS })));
}
return this._progress;
@@ -581,7 +586,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
updateSeverity(severity: Severity): void {
this._severity = severity;
this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.SEVERITY });
this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.SEVERITY });
}
updateMessage(input: NotificationMessage): void {
@@ -591,13 +596,13 @@ export class NotificationViewItem extends Disposable implements INotificationVie
}
this._message = message;
this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.MESSAGE });
this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.MESSAGE });
}
updateActions(actions?: INotificationActions): void {
this.setActions(actions);
this._onDidLabelChange.fire({ kind: NotificationViewItemLabelKind.ACTIONS });
this._onDidChangeLabel.fire({ kind: NotificationViewItemLabelKind.ACTIONS });
}
expand(): void {
@@ -606,7 +611,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
}
this._expanded = true;
this._onDidExpansionChange.fire();
this._onDidChangeExpansion.fire();
}
collapse(skipEvents?: boolean): void {
@@ -617,7 +622,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie
this._expanded = false;
if (!skipEvents) {
this._onDidExpansionChange.fire();
this._onDidChangeExpansion.fire();
}
}
@@ -656,8 +661,8 @@ export class NotificationViewItem extends Disposable implements INotificationVie
export class ChoiceAction extends Action {
private readonly _onDidRun = new Emitter<void>();
readonly onDidRun: Event<void> = this._onDidRun.event;
private readonly _onDidRun = this._register(new Emitter<void>());
readonly onDidRun = this._onDidRun.event;
private readonly _keepOpen: boolean;
@@ -679,12 +684,6 @@ export class ChoiceAction extends Action {
get keepOpen(): boolean {
return this._keepOpen;
}
dispose(): void {
super.dispose();
this._onDidRun.dispose();
}
}
class StatusMessageViewItem {
+3 -3
View File
@@ -5,7 +5,7 @@
import { URI } from 'vs/base/common/uri';
import * as objects from 'vs/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { basename, extname, relativePath } from 'vs/base/common/resources';
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -106,8 +106,8 @@ export class ResourceGlobMatcher extends Disposable {
private static readonly NO_ROOT: string | null = null;
private readonly _onExpressionChange: Emitter<void> = this._register(new Emitter<void>());
readonly onExpressionChange: Event<void> = this._onExpressionChange.event;
private readonly _onExpressionChange = this._register(new Emitter<void>());
readonly onExpressionChange = this._onExpressionChange.event;
private readonly mapRootToParsedExpression: Map<string | null, ParsedExpression> = new Map<string, ParsedExpression>();
private readonly mapRootToExpressionConfig: Map<string | null, IExpression> = new Map<string, IExpression>();
+21 -15
View File
@@ -57,12 +57,12 @@ export interface IViewContainerDescriptor {
export interface IViewContainersRegistry {
/**
* An event that is triggerred when a view container is registered.
* An event that is triggered when a view container is registered.
*/
readonly onDidRegister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
/**
* An event that is triggerred when a view container is deregistered.
* An event that is triggered when a view container is deregistered.
*/
readonly onDidDeregister: Event<{ viewContainer: ViewContainer, viewContainerLocation: ViewContainerLocation }>;
@@ -213,6 +213,7 @@ export interface IViewDescriptorCollection extends IDisposable {
export interface IViewContentDescriptor {
readonly content: string;
readonly when?: ContextKeyExpr | 'default';
}
export interface IViewsRegistry {
@@ -235,9 +236,13 @@ export interface IViewsRegistry {
getViewContainer(id: string): ViewContainer | null;
readonly onDidChangeEmptyViewContent: Event<string>;
registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable;
getEmptyViewContent(id: string): IViewContentDescriptor[];
readonly onDidChangeViewWelcomeContent: Event<string>;
registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable;
getViewWelcomeContent(id: string): IViewContentDescriptor[];
}
function compareViewContentDescriptors(a: IViewContentDescriptor, b: IViewContentDescriptor): number {
return a.content < b.content ? -1 : 1;
}
class ViewsRegistry extends Disposable implements IViewsRegistry {
@@ -251,12 +256,12 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>());
readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event;
private readonly _onDidChangeEmptyViewContent: Emitter<string> = this._register(new Emitter<string>());
readonly onDidChangeEmptyViewContent: Event<string> = this._onDidChangeEmptyViewContent.event;
private readonly _onDidChangeViewWelcomeContent: Emitter<string> = this._register(new Emitter<string>());
readonly onDidChangeViewWelcomeContent: Event<string> = this._onDidChangeViewWelcomeContent.event;
private _viewContainers: ViewContainer[] = [];
private _views: Map<ViewContainer, IViewDescriptor[]> = new Map<ViewContainer, IViewDescriptor[]>();
private _emptyViewContents = new SetMap<string, IViewContentDescriptor>();
private _viewWelcomeContents = new SetMap<string, IViewContentDescriptor>();
registerViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
this.addViews(views, viewContainer);
@@ -306,19 +311,20 @@ class ViewsRegistry extends Disposable implements IViewsRegistry {
return null;
}
registerEmptyViewContent(id: string, viewContent: IViewContentDescriptor): IDisposable {
this._emptyViewContents.add(id, viewContent);
this._onDidChangeEmptyViewContent.fire(id);
registerViewWelcomeContent(id: string, viewContent: IViewContentDescriptor): IDisposable {
this._viewWelcomeContents.add(id, viewContent);
this._onDidChangeViewWelcomeContent.fire(id);
return toDisposable(() => {
this._emptyViewContents.delete(id, viewContent);
this._onDidChangeEmptyViewContent.fire(id);
this._viewWelcomeContents.delete(id, viewContent);
this._onDidChangeViewWelcomeContent.fire(id);
});
}
getEmptyViewContent(id: string): IViewContentDescriptor[] {
getViewWelcomeContent(id: string): IViewContentDescriptor[] {
const result: IViewContentDescriptor[] = [];
this._emptyViewContents.forEach(id, descriptor => result.push(descriptor));
result.sort(compareViewContentDescriptors);
this._viewWelcomeContents.forEach(id, descriptor => result.push(descriptor));
return result;
}
@@ -6,7 +6,7 @@
import 'vs/css!./bulkEdit';
import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService';
import { WorkspaceEdit } from 'vs/editor/common/modes';
import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree';
import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement, BulkEditSorter } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree';
import { FuzzyScore } from 'vs/base/common/filters';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
@@ -135,6 +135,7 @@ export class BulkEditPane extends ViewPane {
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
keyboardNavigationLabelProvider: new BulkEditNaviLabelProvider(),
sorter: new BulkEditSorter()
}
);
@@ -99,6 +99,15 @@ export class BulkFileOperation {
this.newUri = edit.newUri;
}
}
needsConfirmation(): boolean {
for (let [, edit] of this.originalEdits) {
if (!this.parent.checked.isChecked(edit)) {
return true;
}
}
return false;
}
}
export class BulkCategory {
@@ -230,7 +239,7 @@ export class BulkFileOperations {
}
operationByResource.forEach(value => this.fileOperations.push(value));
operationByCategory.forEach(value => value.metadata.needsConfirmation ? this.categories.unshift(value) : this.categories.push(value));
operationByCategory.forEach(value => this.categories.push(value));
// "correct" invalid parent-check child states that is
// unchecked file edits (rename, create, delete) uncheck
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeSorter } from 'vs/base/browser/ui/tree/tree';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
@@ -24,6 +24,7 @@ import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { basename } from 'vs/base/common/resources';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { WorkspaceFileEdit } from 'vs/editor/common/modes';
import { compare } from 'vs/base/common/strings';
// --- VIEW MODEL
@@ -248,6 +249,39 @@ export class BulkEditDataSource implements IAsyncDataSource<BulkFileOperations,
}
}
export class BulkEditSorter implements ITreeSorter<BulkEditElement> {
compare(a: BulkEditElement, b: BulkEditElement): number {
if (a instanceof CategoryElement && b instanceof CategoryElement) {
//
const aConfirm = BulkEditSorter._needsConfirmation(a.category);
const bConfirm = BulkEditSorter._needsConfirmation(b.category);
if (aConfirm === bConfirm) {
return a.category.metadata.label.localeCompare(b.category.metadata.label);
} else if (aConfirm) {
return -1;
} else {
return 1;
}
}
if (a instanceof FileElement && b instanceof FileElement) {
return compare(a.edit.uri.toString(), b.edit.uri.toString());
}
if (a instanceof TextEditElement && b instanceof TextEditElement) {
return Range.compareRangesUsingStarts(a.edit.textEdit.edit.range, b.edit.textEdit.edit.range);
}
return 0;
}
private static _needsConfirmation(a: BulkCategory): boolean {
return a.fileOperations.some(ops => ops.needsConfirmation());
}
}
// --- ACCESSI
export class BulkEditAccessibilityProvider implements IAccessibilityProvider<BulkEditElement> {
@@ -9,6 +9,7 @@ import './diffEditorHelper';
import './inspectKeybindings';
import './largeFileOptimizations';
import './inspectEditorTokens/inspectEditorTokens';
import './saveParticipants';
import './toggleMinimap';
import './toggleMultiCursorModifier';
import './toggleRenderControlCharacter';
@@ -0,0 +1,316 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import * as strings from 'vs/base/common/strings';
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ITextModel } from 'vs/editor/common/model';
import { CodeAction, CodeActionTriggerType } from 'vs/editor/common/modes';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/types';
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProgressStep, IProgress } from 'vs/platform/progress/common/progress';
import { IResolvedTextFileEditorModel, ITextFileService, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution, Extensions as WorkbenchContributionsExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
class TrimWhitespaceParticipant implements ITextFileSaveParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
private doTrimTrailingWhitespace(model: ITextModel, isAutoSaved: boolean): void {
let prevSelection: Selection[] = [];
let cursors: Position[] = [];
const editor = findEditor(model, this.codeEditorService);
if (editor) {
// Find `prevSelection` in any case do ensure a good undo stack when pushing the edit
// Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump
prevSelection = editor.getSelections();
if (isAutoSaved) {
cursors = prevSelection.map(s => s.getPosition());
const snippetsRange = SnippetController2.get(editor).getSessionEnclosingRange();
if (snippetsRange) {
for (let lineNumber = snippetsRange.startLineNumber; lineNumber <= snippetsRange.endLineNumber; lineNumber++) {
cursors.push(new Position(lineNumber, model.getLineMaxColumn(lineNumber)));
}
}
}
}
const ops = trimTrailingWhitespace(model, cursors);
if (!ops.length) {
return; // Nothing to do
}
model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection);
}
}
function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null {
let candidate: IActiveCodeEditor | null = null;
if (model.isAttachedToEditor()) {
for (const editor of codeEditorService.listCodeEditors()) {
if (editor.hasModel() && editor.getModel() === model) {
if (editor.hasTextFocus()) {
return editor; // favour focused editor if there are multiple
}
candidate = editor;
}
}
}
return candidate;
}
export class FinalNewLineParticipant implements ITextFileSaveParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doInsertFinalNewLine(model.textEditorModel);
}
}
private doInsertFinalNewLine(model: ITextModel): void {
const lineCount = model.getLineCount();
const lastLine = model.getLineContent(lineCount);
const lastLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(lastLine) === -1;
if (!lineCount || lastLineIsEmptyOrWhitespace) {
return;
}
const edits = [EditOperation.insert(new Position(lineCount, model.getLineMaxColumn(lineCount)), model.getEOL())];
const editor = findEditor(model, this.codeEditorService);
if (editor) {
editor.executeEdits('insertFinalNewLine', edits, editor.getSelections());
} else {
model.pushEditOperations([], edits, () => null);
}
}
}
export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService
) {
// Nothing
}
async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise<void> {
if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) {
this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
/**
* returns 0 if the entire file is empty or whitespace only
*/
private findLastLineWithContent(model: ITextModel): number {
for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) {
const lineContent = model.getLineContent(lineNumber);
if (strings.lastNonWhitespaceIndex(lineContent) !== -1) {
// this line has content
return lineNumber;
}
}
// no line has content
return 0;
}
private doTrimFinalNewLines(model: ITextModel, isAutoSaved: boolean): void {
const lineCount = model.getLineCount();
// Do not insert new line if file does not end with new line
if (lineCount === 1) {
return;
}
let prevSelection: Selection[] = [];
let cannotTouchLineNumber = 0;
const editor = findEditor(model, this.codeEditorService);
if (editor) {
prevSelection = editor.getSelections();
if (isAutoSaved) {
for (let i = 0, len = prevSelection.length; i < len; i++) {
const positionLineNumber = prevSelection[i].positionLineNumber;
if (positionLineNumber > cannotTouchLineNumber) {
cannotTouchLineNumber = positionLineNumber;
}
}
}
}
const lastLineNumberWithContent = this.findLastLineWithContent(model);
const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1);
const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount)));
if (deletionRange.isEmpty()) {
return;
}
model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection);
if (editor) {
editor.setSelections(prevSelection);
}
}
}
class FormatOnSaveParticipant implements ITextFileSaveParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
// Nothing
}
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
const model = editorModel.textEditorModel;
const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri };
if (env.reason === SaveReason.AUTO || !this.configurationService.getValue('editor.formatOnSave', overrides)) {
return undefined;
}
progress.report({ message: localize('formatting', "Formatting") });
const editorOrModel = findEditor(model, this.codeEditorService) || model;
await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token);
}
}
class CodeActionOnSaveParticipant implements ITextFileSaveParticipant {
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) { }
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
if (env.reason === SaveReason.AUTO) {
return undefined;
}
const model = editorModel.textEditorModel;
const settingsOverrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.resource };
const setting = this.configurationService.getValue<{ [kind: string]: boolean }>('editor.codeActionsOnSave', settingsOverrides);
if (!setting) {
return undefined;
}
const codeActionsOnSave = Object.keys(setting)
.filter(x => setting[x]).map(x => new CodeActionKind(x))
.sort((a, b) => {
if (CodeActionKind.SourceFixAll.contains(a)) {
if (CodeActionKind.SourceFixAll.contains(b)) {
return 0;
}
return -1;
}
if (CodeActionKind.SourceFixAll.contains(b)) {
return 1;
}
return 0;
});
if (!codeActionsOnSave.length) {
return undefined;
}
const excludedActions = Object.keys(setting)
.filter(x => setting[x] === false)
.map(x => new CodeActionKind(x));
progress.report({ message: localize('codeaction', "Quick Fixes") });
await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token);
}
private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise<void> {
for (const codeActionKind of codeActionsOnSave) {
const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token);
try {
await this.applyCodeActions(actionsToRun.validActions);
} catch {
// Failure to apply a code action should not block other on save actions
} finally {
actionsToRun.dispose();
}
}
}
private async applyCodeActions(actionsToRun: readonly CodeAction[]) {
for (const action of actionsToRun) {
await this.instantiationService.invokeFunction(applyCodeAction, action);
}
}
private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) {
return getCodeActions(model, model.getFullModelRange(), {
type: CodeActionTriggerType.Auto,
filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true },
}, token);
}
}
export class SaveParticipantsContribution extends Disposable implements IWorkbenchContribution {
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ITextFileService private readonly textFileService: ITextFileService
) {
super();
this.registerSaveParticipants();
}
private registerSaveParticipants(): void {
this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimWhitespaceParticipant)));
this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(CodeActionOnSaveParticipant)));
this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FormatOnSaveParticipant)));
this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(FinalNewLineParticipant)));
this._register(this.textFileService.files.addSaveParticipant(this.instantiationService.createInstance(TrimFinalNewLinesParticipant)));
}
}
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchContributionsExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(SaveParticipantsContribution, LifecyclePhase.Restored);
@@ -5,9 +5,9 @@
import * as assert from 'assert';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/api/browser/mainThreadSaveParticipant';
import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices';
import { toResource } from 'vs/base/test/common/utils';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Range } from 'vs/editor/common/core/range';
@@ -40,7 +40,7 @@ CommandsRegistry.registerCommand('_workbench.openWith', (accessor: ServicesAcces
// #region Reopen With
const REOPEN_WITH_COMMAND_ID = 'reOpenWith';
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With'), original: 'Reopen With' };
const REOPEN_WITH_TITLE = { value: nls.localize('reopenWith.title', 'Reopen With...'), original: 'Reopen With' };
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: REOPEN_WITH_COMMAND_ID,
@@ -83,6 +83,17 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
when: CONTEXT_HAS_CUSTOM_EDITORS,
});
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: REOPEN_WITH_COMMAND_ID,
title: REOPEN_WITH_TITLE,
category: viewCategory,
},
group: '3_open',
order: 20,
when: CONTEXT_HAS_CUSTOM_EDITORS,
});
// #endregion
@@ -19,7 +19,6 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { once } from 'vs/base/common/functional';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
@@ -233,11 +232,3 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
templateData.toDispose.dispose();
}
}
export abstract class BaseDebugViewPane extends ViewPane {
render(): void {
super.render();
dom.addClass(this.element, 'debug-pane');
}
}
@@ -28,14 +28,13 @@ import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ILabelService } from 'vs/platform/label/common/label';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Gesture } from 'vs/base/browser/touch';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
const $ = dom.$;
@@ -54,7 +53,7 @@ export function getExpandedBodySize(model: IDebugModel): number {
return Math.min(MAX_VISIBLE_BREAKPOINTS, length) * 22;
}
export class BreakpointsView extends BaseDebugViewPane {
export class BreakpointsView extends ViewPane {
private list!: WorkbenchList<IEnablement>;
private needsRefresh = false;
@@ -82,6 +81,7 @@ export class BreakpointsView extends BaseDebugViewPane {
public renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-breakpoints');
const delegate = new BreakpointsDelegate(this.debugService);
@@ -13,12 +13,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { IAction, Action } from 'vs/base/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ILabelService } from 'vs/platform/label/common/label';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
@@ -74,7 +74,7 @@ export function getContextForContributedActions(element: CallStackItem | null):
return '';
}
export class CallStackView extends BaseDebugViewPane {
export class CallStackView extends ViewPane {
private pauseMessage!: HTMLSpanElement;
private pauseMessageLabel!: HTMLSpanElement;
private onCallStackChangeScheduler: RunOnceScheduler;
@@ -158,7 +158,7 @@ export class CallStackView extends BaseDebugViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-call-stack');
const treeContainer = renderViewTree(container);
@@ -136,17 +136,19 @@ function getWordToLineNumbersMap(model: ITextModel | null): Map<string, number[]
model.forceTokenization(lineNumber);
const lineTokens = model.getLineTokens(lineNumber);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
// Token is a word and not a comment
if (tokenType === StandardTokenType.Other) {
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
if (wordMatch) {
const word = wordMatch[0];
if (!result.has(word)) {
result.set(word, []);
@@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { Range, IRange } from 'vs/editor/common/core/range';
import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDebugService, IExpression, IExpressionContainer, IStackFrame } from 'vs/workbench/contrib/debug/common/debug';
@@ -30,6 +30,8 @@ import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
import { coalesce } from 'vs/base/common/arrays';
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
import { EvaluatableExpressionProviderRegistry } from 'vs/editor/common/modes';
import { CancellationToken } from 'vs/base/common/cancellation';
const $ = dom.$;
const MAX_TREE_HEIGHT = 324;
@@ -107,6 +109,7 @@ export class DebugHoverWidget implements IContentWidget {
accessibilityProvider: new DebugHoverAccessibilityProvider(),
mouseSupport: false,
horizontalScrolling: true,
useShadows: false,
overrideStyles: {
listBackground: editorHoverBackground
}
@@ -174,18 +177,52 @@ export class DebugHoverWidget implements IContentWidget {
}
async showAt(range: Range, focus: boolean): Promise<void> {
const pos = range.getStartPosition();
const session = this.debugService.getViewModel().focusedSession;
if (!this.editor.hasModel()) {
if (!session || !this.editor.hasModel()) {
return Promise.resolve(this.hide());
}
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(start - 1, end);
if (!matchingExpression || !session) {
const model = this.editor.getModel();
const pos = range.getStartPosition();
let rng: IRange | undefined = undefined;
let matchingExpression: string | undefined;
if (EvaluatableExpressionProviderRegistry.has(model)) {
const supports = EvaluatableExpressionProviderRegistry.ordered(model);
const promises = supports.map(support => {
return Promise.resolve(support.provideEvaluatableExpression(model, pos, CancellationToken.None)).then(expression => {
return expression;
}, err => {
//onUnexpectedExternalError(err);
return undefined;
});
});
const results = await Promise.all(promises).then(coalesce);
if (results.length > 0) {
matchingExpression = results[0].expression;
rng = results[0].range;
if (!matchingExpression) {
const lineContent = model.getLineContent(pos.lineNumber);
matchingExpression = lineContent.substring(rng.startColumn - 1, rng.endColumn);
}
}
} else { // old one-size-fits-all strategy
const lineContent = model.getLineContent(pos.lineNumber);
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
// use regex to extract the sub-expression #9821
matchingExpression = lineContent.substring(start - 1, end);
rng = new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length);
}
if (!matchingExpression) {
return Promise.resolve(this.hide());
}
@@ -202,13 +239,15 @@ export class DebugHoverWidget implements IContentWidget {
if (!expression || (expression instanceof Expression && !expression.available)) {
this.hide();
return undefined;
return;
}
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length),
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
}]);
if (rng) {
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
range: rng,
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
}]);
}
return this.doShow(pos, expression, focus);
}
@@ -7,12 +7,12 @@ import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { normalize, isAbsolute, posix } from 'vs/base/common/path';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -402,7 +402,7 @@ function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement
};
}
export class LoadedScriptsView extends BaseDebugViewPane {
export class LoadedScriptsView extends ViewPane {
private treeContainer!: HTMLElement;
private loadedScriptsItemType: IContextKey<string>;
@@ -435,6 +435,7 @@ export class LoadedScriptsView extends BaseDebugViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-loaded-scripts');
dom.addClass(container, 'show-file-icons');
@@ -75,7 +75,7 @@
/* Expressions */
.monaco-workbench .debug-viewlet .monaco-list-row .expression,
.monaco-workbench .debug-pane .monaco-list-row .expression,
.monaco-workbench .debug-hover-widget .monaco-list-row .expression {
font-size: 13px;
overflow: hidden;
@@ -84,7 +84,7 @@
white-space: pre;
}
.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression,
.monaco-workbench.mac .debug-pane .monaco-list-row .expression,
.monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression {
font-size: 11px;
}
@@ -12,7 +12,6 @@
user-select: text;
-webkit-user-select: text;
word-break: break-all;
padding: 4px 5px;
}
.monaco-editor .debug-hover-widget .complex-value {
@@ -62,6 +61,7 @@
overflow: auto;
font-family: var(--monaco-monospace-font);
max-height: 500px;
padding: 4px 5px;
}
.monaco-editor .debug-hover-widget .error {
@@ -601,7 +601,7 @@ export class RawDebugSession implements IDisposable {
private send<R extends DebugProtocol.Response>(command: string, args: any, token?: CancellationToken, timeout?: number): Promise<R> {
return new Promise<DebugProtocol.Response>((completeDispatch, errorDispatch) => {
if (!this.debugAdapter) {
errorDispatch(new Error('no debug adapter found'));
errorDispatch(new Error(nls.localize('noDebugAdapter', "No debug adapter found. Can not send '{0}'.", command)));
return;
}
let cancelationListener: IDisposable;
@@ -20,7 +20,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { equals } from 'vs/base/common/arrays';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -170,6 +170,7 @@ export class StartView extends ViewPane {
}));
attachButtonStyler(this.debugButton, this.themeService);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-start-view');
this.secondMessageContainer = $('.section');
@@ -12,12 +12,12 @@ import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewMod
import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { IAction, Action } from 'vs/base/common/actions';
import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
@@ -39,7 +39,7 @@ let forgetScopes = true;
export const variableSetEmitter = new Emitter<void>();
export class VariablesView extends BaseDebugViewPane {
export class VariablesView extends ViewPane {
private onFocusStackFrameScheduler: RunOnceScheduler;
private needsRefresh = false;
@@ -90,6 +90,7 @@ export class VariablesView extends BaseDebugViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-variables');
const treeContainer = renderViewTree(container);
@@ -16,9 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IAction, Action } from 'vs/base/common/actions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
@@ -38,7 +38,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
let ignoreVariableSetEmitter = false;
let useCachedEvaluation = false;
export class WatchExpressionsView extends BaseDebugViewPane {
export class WatchExpressionsView extends ViewPane {
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private needsRefresh = false;
@@ -67,6 +67,7 @@ export class WatchExpressionsView extends BaseDebugViewPane {
renderBody(container: HTMLElement): void {
super.renderBody(container);
dom.addClass(this.element, 'debug-pane');
dom.addClass(container, 'debug-watch');
const treeContainer = renderViewTree(container);
@@ -14,7 +14,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
@@ -47,6 +47,7 @@ import { IViewContainersRegistry, ViewContainerLocation, Extensions as ViewConta
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -290,6 +291,30 @@ CommandsRegistry.registerCommand({
}
});
CommandsRegistry.registerCommand({
id: 'workbench.extensions.search',
description: {
description: localize('workbench.extensions.search.description', "Search for a specific extension"),
args: [
{
name: localize('workbench.extensions.search.arg.name', "Query to use in search"),
schema: { 'type': 'string' }
}
]
},
handler: async (accessor, query: string = '') => {
const viewletService = accessor.get(IViewletService);
const viewlet = await viewletService.openViewlet(VIEWLET_ID, true);
if (!viewlet) {
return;
}
(viewlet.getViewPaneContainer() as IExtensionsViewPaneContainer).search(query);
viewlet.focus();
}
});
// File menu registration
MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
@@ -64,7 +64,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa
this._register(textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider));
// Set as save error handler to service for text files
this.textFileService.saveErrorHandler = this;
this.textFileService.files.saveErrorHandler = this;
this.registerListeners();
}
@@ -676,7 +676,11 @@ export class ExplorerView extends ViewPane {
if (item.isDisposed) {
return this.onSelectResource(resource, reveal, retry + 1);
}
this.tree.reveal(item, 0.5);
// Don't scroll to the item if it's already visible
if (this.tree.getRelativeTop(item) === null) {
this.tree.reveal(item, 0.5);
}
}
this.tree.setFocus([item]);
@@ -29,13 +29,13 @@ export class OpenInDesktopIndicator extends Disposable implements IWorkbenchCont
) {
super();
const links = environmentService.options?.applicationLinkProvider?.();
const links = environmentService.options?.applicationLinks;
if (Array.isArray(links) && links?.length > 0) {
this.installOpenInDesktopIndicator(links);
}
}
private installOpenInDesktopIndicator(links: IApplicationLink[]): void {
private installOpenInDesktopIndicator(links: readonly IApplicationLink[]): void {
// Register action to trigger "Open In Desktop"
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
@@ -71,7 +71,7 @@ export class OpenInDesktopAction extends Action {
}
async run(): Promise<boolean> {
const links = this.environmentService.options?.applicationLinkProvider?.();
const links = this.environmentService.options?.applicationLinks;
if (Array.isArray(links)) {
if (links.length === 1) {
return this.openApplicationLink(links[0]);
@@ -83,7 +83,7 @@ export class OpenInDesktopAction extends Action {
return true;
}
private async runWithPicker(links: IApplicationLink[]): Promise<boolean> {
private async runWithPicker(links: readonly IApplicationLink[]): Promise<boolean> {
// Show a picker with choices
const quickPick = this.quickInputService.createQuickPick<IApplicationLink>();
@@ -27,7 +27,7 @@ import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IResourceInput, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchDataTree } from 'vs/platform/list/browser/listService';
@@ -37,7 +37,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline';
import { FuzzyScore } from 'vs/base/common/filters';
import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineFilter } from 'vs/editor/contrib/documentSymbols/outlineTree';
@@ -49,6 +49,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco
import { MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
class RequestState {
@@ -261,7 +262,7 @@ export class OutlinePane extends ViewPane {
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IThemeService private readonly _themeService: IThemeService,
@IStorageService private readonly _storageService: IStorageService,
@IEditorService private readonly _editorService: IEditorService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@@ -347,7 +348,7 @@ export class OutlinePane extends ViewPane {
this._disposables.push(this._tree);
this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this));
this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => {
this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views }) => {
if (views.some(v => v.id === this.id)) {
this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } });
}
@@ -629,15 +630,18 @@ export class OutlinePane extends ViewPane {
}
private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise<void> {
await this._editorService.openEditor({
resource: model.textModel.uri,
options: {
preserveFocus: !focus,
selection: Range.collapseToStart(element.symbol.selectionRange),
selectionRevealType: TextEditorSelectionRevealType.NearTop,
}
} as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP);
await this._editorService.openCodeEditor(
{
resource: model.textModel.uri,
options: {
preserveFocus: !focus,
selection: Range.collapseToStart(element.symbol.selectionRange),
selectionRevealType: TextEditorSelectionRevealType.NearTop,
}
},
this._editorService.getActiveCodeEditor(),
aside
);
}
private _revealEditorSelection(model: OutlineModel, selection: Selection): void {
@@ -29,7 +29,7 @@ import { SelectionHighlighter } from 'vs/editor/contrib/multicursor/multicursor'
import * as nls from 'vs/nls';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -984,7 +984,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor {
private static _getContributions(): IEditorContributionDescription[] {
const skipContributions = [FoldingController.ID, SelectionHighlighter.ID, FindController.ID];
const contributions = EditorExtensionsRegistry.getEditorContributions().filter(c => skipContributions.indexOf(c.id) === -1);
contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution });
contributions.push({ id: DefaultSettingsEditorContribution.ID, ctor: DefaultSettingsEditorContribution as IConstructorSignature1<ICodeEditor, editorCommon.IEditorContribution> });
return contributions;
}
@@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { RunOnceScheduler } from 'vs/base/common/async';
import { URI } from 'vs/base/common/uri';
import { isEqual } from 'vs/base/common/resources';
import { isMacintosh, isNative } from 'vs/base/common/platform';
import { isMacintosh, isNative, isLinux } from 'vs/base/common/platform';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -26,6 +26,7 @@ interface IConfiguration extends IWindowsConfiguration {
telemetry: { enableCrashReporter: boolean };
workbench: { list: { horizontalScrolling: boolean } };
debug: { console: { wordWrap: boolean } };
editor: { accessibilitySupport: 'on' | 'off' | 'auto' };
}
export class SettingsChangeRelauncher extends Disposable implements IWorkbenchContribution {
@@ -38,6 +39,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
private enableCrashReporter: boolean | undefined;
private treeHorizontalScrolling: boolean | undefined;
private debugConsoleWordWrap: boolean | undefined;
private accessibilitySupport: 'on' | 'off' | 'auto' | undefined;
constructor(
@IHostService private readonly hostService: IHostService,
@@ -103,6 +105,14 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
this.enableCrashReporter = config.telemetry.enableCrashReporter;
changed = true;
}
// On linux turning on accessibility support will also pass this flag to the chrome renderer, thus a restart is required
if (isLinux && typeof config.editor?.accessibilitySupport === 'string' && config.editor.accessibilitySupport !== this.accessibilitySupport) {
this.accessibilitySupport = config.editor.accessibilitySupport;
if (this.accessibilitySupport === 'on') {
changed = true;
}
}
}
// Notify only when changed and we are the focused window (avoids notification spam across windows)

Some files were not shown because too many files have changed in this diff Show More