Merge branch 'master' into issue/88294

This commit is contained in:
João Moreno
2020-02-18 10:03:44 +01:00
committed by GitHub
248 changed files with 6078 additions and 2473 deletions
+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"
+1 -1
View File
@@ -295,7 +295,7 @@ class TaskDetector {
private updateProvider(): void {
if (!this.taskProvider && this.detectors.size > 0) {
const thisCapture = this;
this.taskProvider = vscode.workspace.registerTaskProvider('grunt', {
this.taskProvider = vscode.tasks.registerTaskProvider('grunt', {
provideTasks: (): Promise<vscode.Task[]> => {
return thisCapture.getTasks();
},
+1 -1
View File
@@ -277,7 +277,7 @@ class TaskDetector {
private updateProvider(): void {
if (!this.taskProvider && this.detectors.size > 0) {
const thisCapture = this;
this.taskProvider = vscode.workspace.registerTaskProvider('gulp', {
this.taskProvider = vscode.tasks.registerTaskProvider('gulp', {
provideTasks(): Promise<vscode.Task[]> {
return thisCapture.getTasks();
},
@@ -515,22 +515,6 @@ connection.onRequest(MatchingTagPositionRequest.type, (params, token) => {
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
});
connection.onRequest(MatchingTagPositionRequest.type, (params, token) => {
return runSafe(() => {
const document = documents.get(params.textDocument.uri);
if (document) {
const pos = params.position;
if (pos.character > 0) {
const mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1));
if (mode && mode.findMatchingTagPosition) {
return mode.findMatchingTagPosition(document, pos);
}
}
}
return null;
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
});
let semanticTokensProvider: SemanticTokenProvider | undefined;
function getSemanticTokenProvider() {
if (!semanticTokensProvider) {
+1 -1
View File
@@ -269,7 +269,7 @@ class TaskDetector {
private updateProvider(): void {
if (!this.taskProvider && this.detectors.size > 0) {
const thisCapture = this;
this.taskProvider = vscode.workspace.registerTaskProvider('jake', {
this.taskProvider = vscode.tasks.registerTaskProvider('jake', {
provideTasks(): Promise<vscode.Task[]> {
return thisCapture.getTasks();
},
@@ -1,7 +1,7 @@
{
"name": "vscode-json-languageserver",
"description": "JSON language server",
"version": "1.2.2",
"version": "1.2.3",
"author": "Microsoft Corporation",
"license": "MIT",
"engines": {
+1 -1
View File
@@ -70,7 +70,7 @@ function registerTaskProvider(context: vscode.ExtensionContext): vscode.Disposab
context.subscriptions.push(workspaceWatcher);
let provider: vscode.TaskProvider = new NpmTaskProvider();
let disposable = vscode.workspace.registerTaskProvider('npm', provider);
let disposable = vscode.tasks.registerTaskProvider('npm', provider);
context.subscriptions.push(disposable);
return disposable;
}
@@ -247,7 +247,7 @@ export default class PHPValidationProvider {
}
};
let options = vscode.workspace.rootPath ? { cwd: vscode.workspace.rootPath } : undefined;
let options = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders[0]) ? { cwd: vscode.workspace.workspaceFolders[0].uri.fsPath } : undefined;
let args: string[];
if (this.trigger === RunTrigger.onSave) {
args = PHPValidationProvider.FileArgs.slice(0);
@@ -1,6 +1,4 @@
// @ts-check
// todo@jackson
/* eslint code-no-unexternalized-strings: 0 */
const mappings = [
['bat', 'source.batchfile'],
@@ -40,6 +38,7 @@ const mappings = [
['perl', 'source.perl'],
['php', 'source.php'],
['pl', 'source.perl'],
['pm', 'source.perl'],
['ps1', 'source.powershell'],
['pug', 'text.pug'],
['py', 'source.python'],
@@ -104,43 +103,43 @@ mappings.forEach(([ext, scope, regexp]) =>
repository[ext] = {
name: scopes.resultBlock.meta,
begin: `^(?!\\s)(.*?)([^\\\\\\/\\n]*${regexp || `\\.${ext}`})(:)$`,
end: "^(?!\\s)",
end: '^(?!\\s)',
beginCaptures: {
"0": { name: scopes.resultBlock.path.meta },
"1": { name: scopes.resultBlock.path.dirname },
"2": { name: scopes.resultBlock.path.basename },
"3": { name: scopes.resultBlock.path.colon },
'0': { name: scopes.resultBlock.path.meta },
'1': { name: scopes.resultBlock.path.dirname },
'2': { name: scopes.resultBlock.path.basename },
'3': { name: scopes.resultBlock.path.colon },
},
patterns: [
{
name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaMultiLine].join(' '),
begin: "^ ((\\d+) )",
while: "^ (?:((\\d+)(:))|((\\d+) ))",
begin: '^ ((\\d+) )',
while: '^ (?:((\\d+)(:))|((\\d+) ))',
beginCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaContext },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
'0': { name: scopes.resultBlock.result.prefix.meta },
'1': { name: scopes.resultBlock.result.prefix.metaContext },
'2': { name: scopes.resultBlock.result.prefix.lineNumber },
},
whileCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaMatch },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
'0': { name: scopes.resultBlock.result.prefix.meta },
'1': { name: scopes.resultBlock.result.prefix.metaMatch },
'2': { name: scopes.resultBlock.result.prefix.lineNumber },
'3': { name: scopes.resultBlock.result.prefix.colon },
"4": { name: scopes.resultBlock.result.prefix.metaContext },
"5": { name: scopes.resultBlock.result.prefix.lineNumber },
'4': { name: scopes.resultBlock.result.prefix.metaContext },
'5': { name: scopes.resultBlock.result.prefix.lineNumber },
},
patterns: [{ include: scope }]
},
{
begin: "^ ((\\d+)(:))",
while: "(?=not)possible",
begin: '^ ((\\d+)(:))',
while: '(?=not)possible',
name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaSingleLine].join(' '),
beginCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaMatch },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
'0': { name: scopes.resultBlock.result.prefix.meta },
'1': { name: scopes.resultBlock.result.prefix.metaMatch },
'2': { name: scopes.resultBlock.result.prefix.lineNumber },
'3': { name: scopes.resultBlock.result.prefix.colon },
},
patterns: [{ include: scope }]
}
@@ -149,10 +148,10 @@ mappings.forEach(([ext, scope, regexp]) =>
const header = [
{
begin: "^(# Query): ",
end: "\n",
begin: '^(# Query): ',
end: '\n',
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
beginCaptures: { '1': { name: scopes.header.key }, },
patterns: [
{
match: '(\\\\n)|(\\\\\\\\)',
@@ -169,10 +168,10 @@ const header = [
]
},
{
begin: "^(# Flags): ",
end: "\n",
begin: '^(# Flags): ',
end: '\n',
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
beginCaptures: { '1': { name: scopes.header.key }, },
patterns: [
{
match: '(RegExp|CaseSensitive|IgnoreExcludeSettings|WordMatch)',
@@ -182,10 +181,10 @@ const header = [
]
},
{
begin: "^(# ContextLines): ",
end: "\n",
begin: '^(# ContextLines): ',
end: '\n',
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
beginCaptures: { '1': { name: scopes.header.key }, },
patterns: [
{
match: '\\d',
@@ -195,42 +194,42 @@ const header = [
]
},
{
match: "^(# (?:Including|Excluding)): (.*)$",
match: '^(# (?:Including|Excluding)): (.*)$',
name: scopes.header.meta,
captures: {
"1": { name: scopes.header.key },
"2": { name: scopes.header.value }
'1': { name: scopes.header.key },
'2': { name: scopes.header.value }
}
},
];
const plainText = [
{
match: "^(?!\\s)(.*?)([^\\\\\\/\\n]*)(:)$",
match: '^(?!\\s)(.*?)([^\\\\\\/\\n]*)(:)$',
name: [scopes.resultBlock.meta, scopes.resultBlock.path.meta].join(' '),
captures: {
"1": { name: scopes.resultBlock.path.dirname },
"2": { name: scopes.resultBlock.path.basename },
"3": { name: scopes.resultBlock.path.colon }
'1': { name: scopes.resultBlock.path.dirname },
'2': { name: scopes.resultBlock.path.basename },
'3': { name: scopes.resultBlock.path.colon }
}
},
{
match: "^ (?:((\\d+)(:))|((\\d+)( ))(.*))",
match: '^ (?:((\\d+)(:))|((\\d+)( ))(.*))',
name: [scopes.resultBlock.meta, scopes.resultBlock.result.meta].join(' '),
captures: {
"1": { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
'1': { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') },
'2': { name: scopes.resultBlock.result.prefix.lineNumber },
'3': { name: scopes.resultBlock.result.prefix.colon },
"4": { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaContext].join(' ') },
"5": { name: scopes.resultBlock.result.prefix.lineNumber },
'4': { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaContext].join(' ') },
'5': { name: scopes.resultBlock.result.prefix.lineNumber },
}
}
];
const tmLanguage = {
"information_for_contributors": "This file is generated from ./generateTMLanguage.js.",
name: "Search Results",
'information_for_contributors': 'This file is generated from ./generateTMLanguage.js.',
name: 'Search Results',
scopeName: scopes.root,
patterns: [
...header,
@@ -189,6 +189,9 @@
{
"include": "#pl"
},
{
"include": "#pm"
},
{
"include": "#ps1"
},
@@ -3457,6 +3460,92 @@
}
]
},
"pm": {
"name": "meta.resultBlock.search",
"begin": "^(?!\\s)(.*?)([^\\\\\\/\\n]*\\.pm)(:)$",
"end": "^(?!\\s)",
"beginCaptures": {
"0": {
"name": "string meta.path.search"
},
"1": {
"name": "meta.path.dirname.search"
},
"2": {
"name": "meta.path.basename.search"
},
"3": {
"name": "punctuation.separator"
}
},
"patterns": [
{
"name": "meta.resultLine.search meta.resultLine.multiLine.search",
"begin": "^ ((\\d+) )",
"while": "^ (?:((\\d+)(:))|((\\d+) ))",
"beginCaptures": {
"0": {
"name": "constant.numeric.integer meta.resultLinePrefix.search"
},
"1": {
"name": "meta.resultLinePrefix.contextLinePrefix.search"
},
"2": {
"name": "meta.resultLinePrefix.lineNumber.search"
}
},
"whileCaptures": {
"0": {
"name": "constant.numeric.integer meta.resultLinePrefix.search"
},
"1": {
"name": "meta.resultLinePrefix.matchLinePrefix.search"
},
"2": {
"name": "meta.resultLinePrefix.lineNumber.search"
},
"3": {
"name": "punctuation.separator"
},
"4": {
"name": "meta.resultLinePrefix.contextLinePrefix.search"
},
"5": {
"name": "meta.resultLinePrefix.lineNumber.search"
}
},
"patterns": [
{
"include": "source.perl"
}
]
},
{
"begin": "^ ((\\d+)(:))",
"while": "(?=not)possible",
"name": "meta.resultLine.search meta.resultLine.singleLine.search",
"beginCaptures": {
"0": {
"name": "constant.numeric.integer meta.resultLinePrefix.search"
},
"1": {
"name": "meta.resultLinePrefix.matchLinePrefix.search"
},
"2": {
"name": "meta.resultLinePrefix.lineNumber.search"
},
"3": {
"name": "punctuation.separator"
}
},
"patterns": [
{
"include": "source.perl"
}
]
}
]
},
"ps1": {
"name": "meta.resultBlock.search",
"begin": "^(?!\\s)(.*?)([^\\\\\\/\\n]*\\.ps1)(:)$",
@@ -38,7 +38,7 @@ export function activate(
});
registerCommands(commandManager, lazyClientHost, pluginManager);
context.subscriptions.push(vscode.workspace.registerTaskProvider('typescript', new TscTaskProvider(lazyClientHost.map(x => x.serviceClient))));
context.subscriptions.push(vscode.tasks.registerTaskProvider('typescript', new TscTaskProvider(lazyClientHost.map(x => x.serviceClient))));
context.subscriptions.push(new LanguageConfigurationManager());
import('./features/tsconfig').then(module => {
@@ -32,6 +32,7 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => {
};
class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
public constructor(
private readonly client: ITypeScriptServiceClient,
private cachedResponse: CachedResponse<Proto.NavTreeResponse>,
@@ -45,23 +46,27 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider
const args: Proto.FileRequestArgs = { file };
const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', args, token));
if (response.type !== 'response' || !response.body) {
if (response.type !== 'response' || !response.body?.childItems) {
return undefined;
}
let tree = response.body;
if (tree && tree.childItems) {
// The root represents the file. Ignore this when showing in the UI
const result: vscode.DocumentSymbol[] = [];
tree.childItems.forEach(item => TypeScriptDocumentSymbolProvider.convertNavTree(document.uri, result, item));
return result;
// The root represents the file. Ignore this when showing in the UI
const result: vscode.DocumentSymbol[] = [];
for (const item of response.body.childItems) {
TypeScriptDocumentSymbolProvider.convertNavTree(document.uri, result, item);
}
return undefined;
return result;
}
private static convertNavTree(resource: vscode.Uri, bucket: vscode.DocumentSymbol[], item: Proto.NavigationTree): boolean {
private static convertNavTree(
resource: vscode.Uri,
output: vscode.DocumentSymbol[],
item: Proto.NavigationTree,
): boolean {
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item);
if (!shouldInclude && !item.childItems?.length) {
return false;
}
const children = new Set(item.childItems || []);
for (const span of item.spans) {
@@ -83,7 +88,7 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider
}
if (shouldInclude) {
bucket.push(symbolInfo);
output.push(symbolInfo);
}
}
@@ -98,7 +103,6 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider
}
}
export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
@@ -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 {
@@ -145,14 +145,7 @@ suite('window namespace tests', () => {
});
});
// TODO this randomly fails when running against web and visually
// what seems to happen is that the second editor opens and both
// left and right editor show a blinking cursor. Since active editor
// tracking relies on editor focus to function properly, this
// seems to be the root cause of the failure.
// https://github.com/microsoft/vscode/issues/90470
test.skip('active editor not always correct... #49125', async function () {
test('active editor not always correct... #49125', async function () {
const randomFile1 = await createRandomFile();
const randomFile2 = await createRandomFile();
@@ -214,104 +214,94 @@ suite('workspace-namespace', () => {
});
});
test('eol, change via onWillSave', function () {
if (vscode.env.uiKind === vscode.UIKind.Web) {
// TODO@Jo Test seems to fail when running in web due to
// onWillSaveTextDocument not getting called
return this.skip();
}
test('eol, change via onWillSave', async function () {
let called = false;
let sub = vscode.workspace.onWillSaveTextDocument(e => {
called = true;
e.waitUntil(Promise.resolve([vscode.TextEdit.setEndOfLine(vscode.EndOfLine.LF)]));
});
return createRandomFile('foo\r\nbar\r\nbar').then(file => {
return vscode.workspace.openTextDocument(file).then(doc => {
assert.equal(doc.eol, vscode.EndOfLine.CRLF);
const edit = new vscode.WorkspaceEdit();
edit.set(file, [vscode.TextEdit.insert(new vscode.Position(0, 0), '-changes-')]);
const file = await createRandomFile('foo\r\nbar\r\nbar');
const doc = await vscode.workspace.openTextDocument(file);
assert.equal(doc.eol, vscode.EndOfLine.CRLF);
return vscode.workspace.applyEdit(edit).then(success => {
assert.ok(success);
return doc.save();
const edit = new vscode.WorkspaceEdit();
edit.set(file, [vscode.TextEdit.insert(new vscode.Position(0, 0), '-changes-')]);
const successEdit = await vscode.workspace.applyEdit(edit);
assert.ok(successEdit);
}).then(success => {
assert.ok(success);
assert.ok(called);
assert.ok(!doc.isDirty);
assert.equal(doc.eol, vscode.EndOfLine.LF);
sub.dispose();
});
});
});
const successSave = await doc.save();
assert.ok(successSave);
assert.ok(called);
assert.ok(!doc.isDirty);
assert.equal(doc.eol, vscode.EndOfLine.LF);
sub.dispose();
});
test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', () => {
return createRandomFile().then(file => {
let disposables: vscode.Disposable[] = [];
function assertEqualPath(a: string, b: string): void {
assert.ok(pathEquals(a, b), `${a} <-> ${b}`);
}
let onDidOpenTextDocument = false;
disposables.push(vscode.workspace.onDidOpenTextDocument(e => {
assert.ok(pathEquals(e.uri.fsPath, file.fsPath));
onDidOpenTextDocument = true;
}));
test('events: onDidOpenTextDocument, onDidChangeTextDocument, onDidSaveTextDocument', async () => {
const file = await createRandomFile();
let disposables: vscode.Disposable[] = [];
let onDidChangeTextDocument = false;
disposables.push(vscode.workspace.onDidChangeTextDocument(e => {
assert.ok(pathEquals(e.document.uri.fsPath, file.fsPath));
onDidChangeTextDocument = true;
}));
await vscode.workspace.saveAll();
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
assert.ok(pathEquals(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
let pendingAsserts: Function[] = [];
let onDidOpenTextDocument = false;
disposables.push(vscode.workspace.onDidOpenTextDocument(e => {
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidOpenTextDocument = true;
}));
return vscode.workspace.openTextDocument(file).then(doc => {
return vscode.window.showTextDocument(doc).then((editor) => {
return editor.edit((builder) => {
builder.insert(new vscode.Position(0, 0), 'Hello World');
}).then(_applied => {
return doc.save().then(_saved => {
assert.ok(onDidOpenTextDocument);
assert.ok(onDidChangeTextDocument);
assert.ok(onDidSaveTextDocument);
let onDidChangeTextDocument = false;
disposables.push(vscode.workspace.onDidChangeTextDocument(e => {
pendingAsserts.push(() => assertEqualPath(e.document.uri.fsPath, file.fsPath));
onDidChangeTextDocument = true;
}));
disposeAll(disposables);
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
return deleteFile(file);
});
});
});
});
const doc = await vscode.workspace.openTextDocument(file);
const editor = await vscode.window.showTextDocument(doc);
await editor.edit((builder) => {
builder.insert(new vscode.Position(0, 0), 'Hello World');
});
await doc.save();
assert.ok(onDidOpenTextDocument);
assert.ok(onDidChangeTextDocument);
assert.ok(onDidSaveTextDocument);
pendingAsserts.forEach(assert => assert());
disposeAll(disposables);
return deleteFile(file);
});
test('events: onDidSaveTextDocument fires even for non dirty file when saved', () => {
return createRandomFile().then(file => {
let disposables: vscode.Disposable[] = [];
test('events: onDidSaveTextDocument fires even for non dirty file when saved', async () => {
const file = await createRandomFile();
let disposables: vscode.Disposable[] = [];
let pendingAsserts: Function[] = [];
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
assert.ok(pathEquals(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
let onDidSaveTextDocument = false;
disposables.push(vscode.workspace.onDidSaveTextDocument(e => {
pendingAsserts.push(() => assertEqualPath(e.uri.fsPath, file.fsPath));
onDidSaveTextDocument = true;
}));
return vscode.workspace.openTextDocument(file).then(doc => {
return vscode.window.showTextDocument(doc).then(() => {
return vscode.commands.executeCommand('workbench.action.files.save').then(() => {
assert.ok(onDidSaveTextDocument);
const doc = await vscode.workspace.openTextDocument(file);
await vscode.window.showTextDocument(doc);
await vscode.commands.executeCommand('workbench.action.files.save');
disposeAll(disposables);
return deleteFile(file);
});
});
});
});
assert.ok(onDidSaveTextDocument);
pendingAsserts.forEach(fn => fn());
disposeAll(disposables);
return deleteFile(file);
});
test('openTextDocument, with selection', function () {