diff --git a/extensions/git-extended/.vscode/launch.json b/extensions/git-extended/.vscode/launch.json
new file mode 100644
index 00000000000..017c8762415
--- /dev/null
+++ b/extensions/git-extended/.vscode/launch.json
@@ -0,0 +1,18 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch Extension",
+ "type": "extensionHost",
+ "request": "launch",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "--extensionDevelopmentPath=${workspaceFolder}"
+ ],
+ "stopOnEntry": false,
+ "sourceMaps": true,
+ "outFiles": ["${workspaceFolder}/client/out/**/*.js"],
+ "preLaunchTask": "npm"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/extensions/git-extended/.vscode/tasks.json b/extensions/git-extended/.vscode/tasks.json
new file mode 100644
index 00000000000..0a411c1c867
--- /dev/null
+++ b/extensions/git-extended/.vscode/tasks.json
@@ -0,0 +1,9 @@
+{
+ "version": "0.1.0",
+ "command": "npm",
+ "isShellCommand": true,
+ "showOutput": "silent",
+ "args": ["run", "compile"],
+ "isBackground": true,
+ "problemMatcher": "$tsc-watch"
+}
\ No newline at end of file
diff --git a/extensions/git-extended/.vscodeignore b/extensions/git-extended/.vscodeignore
new file mode 100644
index 00000000000..d43a539fddf
--- /dev/null
+++ b/extensions/git-extended/.vscodeignore
@@ -0,0 +1,2 @@
+src/**
+tsconfig.json
\ No newline at end of file
diff --git a/extensions/git-extended/README.md b/extensions/git-extended/README.md
new file mode 100644
index 00000000000..0cb79afb054
--- /dev/null
+++ b/extensions/git-extended/README.md
@@ -0,0 +1,5 @@
+# Git Extended
+
+
+## Notices
+Uses code from github desktop: https://github.com/desktop/desktop
\ No newline at end of file
diff --git a/extensions/git-extended/images/npm_icon.png b/extensions/git-extended/images/npm_icon.png
new file mode 100644
index 00000000000..f7f18b560ee
Binary files /dev/null and b/extensions/git-extended/images/npm_icon.png differ
diff --git a/extensions/git-extended/package.json b/extensions/git-extended/package.json
new file mode 100644
index 00000000000..dc3859758e0
--- /dev/null
+++ b/extensions/git-extended/package.json
@@ -0,0 +1,119 @@
+{
+ "name": "git-extended",
+ "displayName": "Git Extended",
+ "description": "Git Extended",
+ "enableProposedApi": true,
+ "version": "0.0.1",
+ "publisher": "rebornix",
+ "engines": {
+ "vscode": "^1.13.0"
+ },
+ "categories": [
+ "Other"
+ ],
+ "activationEvents": [
+ "onView:commits",
+ "onView:stash",
+ "onView:pr"
+ ],
+ "main": "./out/extension",
+ "contributes": {
+ "views": {
+ "explorer": [
+ {
+ "id": "pr",
+ "name": "Pull Requests"
+ }
+ ]
+ },
+ "commands": [
+ {
+ "command": "commits.revertCommit",
+ "title": "Revert",
+ "icon": {
+ "dark": "resources/icons/dark/clean.svg",
+ "light": "resources/icons/light/clean.svg"
+ }
+ },
+ {
+ "command": "commits.refresh",
+ "title": "Refresh",
+ "icon": {
+ "dark": "resources/icons/dark/refresh.svg",
+ "light": "resources/icons/light/refresh.svg"
+ }
+ },
+ {
+ "command": "pr.refreshList",
+ "title": "Refresh",
+ "icon": {
+ "dark": "resources/icons/dark/refresh.svg",
+ "light": "resources/icons/light/refresh.svg"
+ }
+ },
+ {
+ "command": "stash.apply",
+ "title": "Apply Stash"
+ },
+ {
+ "command": "stash.delete",
+ "title": "Delete Stash"
+ },
+ {
+ "command": "stash.pop",
+ "title": "Pop Stash"
+ },
+ {
+ "command": "stash.stash",
+ "title": "Stash",
+ "icon": {
+ "dark": "resources/icons/dark/stage.svg",
+ "light": "resources/icons/light/stage.svg"
+ }
+ }
+ ],
+ "menus": {
+ "view/title": [
+ {
+ "command": "commits.refresh",
+ "when": "view == commits",
+ "group": "navigation"
+ },
+ {
+ "command": "stash.stash",
+ "when": "view == stash",
+ "group": "navigation"
+ }
+ ],
+ "view/item/context": [
+ {
+ "command": "stash.apply",
+ "when": "view == stash"
+ },
+ {
+ "command": "stash.pop",
+ "when": "view == stash"
+ },
+ {
+ "command": "stash.delete",
+ "when": "view == stash"
+ }
+ ]
+ }
+ },
+ "scripts": {
+ "vscode:prepublish": "tsc -p ./",
+ "compile": "tsc -watch -p ./"
+ },
+ "devDependencies": {
+ "typescript": "^2.1.4",
+ "@types/node": "*"
+ },
+ "dependencies": {
+ "dugite": "^1.28.0",
+ "tmp": "^0.0.31",
+ "octokat": "^0.8.0",
+ "request": "^2.81.0",
+ "lodash": "4.17.5"
+ }
+}
diff --git a/extensions/git-extended/package.nls.json b/extensions/git-extended/package.nls.json
new file mode 100644
index 00000000000..0a9ece35f6e
--- /dev/null
+++ b/extensions/git-extended/package.nls.json
@@ -0,0 +1,11 @@
+{
+ "description": "Extension to add task support for npm scripts.",
+ "displayName": "Npm support for VSCode",
+ "config.npm.autoDetect": "Controls whether auto detection of npm scripts is on or off. Default is on.",
+ "config.npm.runSilent": "Run npm commands with the `--silent` option.",
+ "config.npm.packageManager": "The package manager used to run scripts.",
+ "config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.",
+ "npm.parseError": "Npm task detection: failed to parse the file {0}",
+ "taskdef.script": "The npm script to customize.",
+ "taskdef.path": "The path to the folder of the package.json file that provides the script. Can be ommitted."
+}
diff --git a/extensions/git-extended/resources/icons/checked_checkbox.png b/extensions/git-extended/resources/icons/checked_checkbox.png
new file mode 100644
index 00000000000..f8280bdfc32
Binary files /dev/null and b/extensions/git-extended/resources/icons/checked_checkbox.png differ
diff --git a/extensions/git-extended/resources/icons/dark/check.svg b/extensions/git-extended/resources/icons/dark/check.svg
new file mode 100644
index 00000000000..c225b2f597f
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/clean.svg b/extensions/git-extended/resources/icons/dark/clean.svg
new file mode 100644
index 00000000000..9f175633389
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/clean.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/git.svg b/extensions/git-extended/resources/icons/dark/git.svg
new file mode 100644
index 00000000000..c08b1c2e403
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/git.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/open-change.svg b/extensions/git-extended/resources/icons/dark/open-change.svg
new file mode 100644
index 00000000000..c951728abac
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/open-change.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/open-file.svg b/extensions/git-extended/resources/icons/dark/open-file.svg
new file mode 100644
index 00000000000..f6302185aa4
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/open-file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/refresh.svg b/extensions/git-extended/resources/icons/dark/refresh.svg
new file mode 100644
index 00000000000..d79fdaa4e8e
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/stage.svg b/extensions/git-extended/resources/icons/dark/stage.svg
new file mode 100644
index 00000000000..3475c1e1963
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/stage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-added.svg b/extensions/git-extended/resources/icons/dark/status-added.svg
new file mode 100644
index 00000000000..cdc40f45f16
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-added.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-conflict.svg b/extensions/git-extended/resources/icons/dark/status-conflict.svg
new file mode 100644
index 00000000000..53b243c8b9f
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-conflict.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-copied.svg b/extensions/git-extended/resources/icons/dark/status-copied.svg
new file mode 100644
index 00000000000..7bd78c9427e
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-copied.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-deleted.svg b/extensions/git-extended/resources/icons/dark/status-deleted.svg
new file mode 100644
index 00000000000..e7596e2e2ad
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-deleted.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-ignored.svg b/extensions/git-extended/resources/icons/dark/status-ignored.svg
new file mode 100644
index 00000000000..85abc367a29
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-ignored.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-modified.svg b/extensions/git-extended/resources/icons/dark/status-modified.svg
new file mode 100644
index 00000000000..d0de37d3468
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-modified.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-renamed.svg b/extensions/git-extended/resources/icons/dark/status-renamed.svg
new file mode 100644
index 00000000000..a77fb41179a
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-renamed.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/status-untracked.svg b/extensions/git-extended/resources/icons/dark/status-untracked.svg
new file mode 100644
index 00000000000..c6a48e14f08
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/status-untracked.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/dark/unstage.svg b/extensions/git-extended/resources/icons/dark/unstage.svg
new file mode 100644
index 00000000000..2de46fcf5b5
--- /dev/null
+++ b/extensions/git-extended/resources/icons/dark/unstage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/check.svg b/extensions/git-extended/resources/icons/light/check.svg
new file mode 100644
index 00000000000..d45df06edf8
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/check.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/clean.svg b/extensions/git-extended/resources/icons/light/clean.svg
new file mode 100644
index 00000000000..1fa6ba48a19
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/clean.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/git.svg b/extensions/git-extended/resources/icons/light/git.svg
new file mode 100644
index 00000000000..d1049a44d0d
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/git.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/open-change.svg b/extensions/git-extended/resources/icons/light/open-change.svg
new file mode 100644
index 00000000000..3a205509bca
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/open-change.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/open-file.svg b/extensions/git-extended/resources/icons/light/open-file.svg
new file mode 100644
index 00000000000..fccdf83d467
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/open-file.svg
@@ -0,0 +1,3 @@
+
+]>
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/refresh.svg b/extensions/git-extended/resources/icons/light/refresh.svg
new file mode 100644
index 00000000000..e0345748192
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/refresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/stage.svg b/extensions/git-extended/resources/icons/light/stage.svg
new file mode 100644
index 00000000000..bdecdb0e45b
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/stage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-added.svg b/extensions/git-extended/resources/icons/light/status-added.svg
new file mode 100644
index 00000000000..587fc08f5f5
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-added.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-conflict.svg b/extensions/git-extended/resources/icons/light/status-conflict.svg
new file mode 100644
index 00000000000..b6088ecd083
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-conflict.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-copied.svg b/extensions/git-extended/resources/icons/light/status-copied.svg
new file mode 100644
index 00000000000..151fdb2cdbe
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-copied.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-deleted.svg b/extensions/git-extended/resources/icons/light/status-deleted.svg
new file mode 100644
index 00000000000..7ed166accfe
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-deleted.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-ignored.svg b/extensions/git-extended/resources/icons/light/status-ignored.svg
new file mode 100644
index 00000000000..85abc367a29
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-ignored.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-modified.svg b/extensions/git-extended/resources/icons/light/status-modified.svg
new file mode 100644
index 00000000000..ff338b81410
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-modified.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-renamed.svg b/extensions/git-extended/resources/icons/light/status-renamed.svg
new file mode 100644
index 00000000000..a77fb41179a
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-renamed.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/status-untracked.svg b/extensions/git-extended/resources/icons/light/status-untracked.svg
new file mode 100644
index 00000000000..c6a48e14f08
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/status-untracked.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/light/unstage.svg b/extensions/git-extended/resources/icons/light/unstage.svg
new file mode 100644
index 00000000000..f5d128b2df8
--- /dev/null
+++ b/extensions/git-extended/resources/icons/light/unstage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/extensions/git-extended/resources/icons/unchecked_checkbox.png b/extensions/git-extended/resources/icons/unchecked_checkbox.png
new file mode 100644
index 00000000000..849e857eb9b
Binary files /dev/null and b/extensions/git-extended/resources/icons/unchecked_checkbox.png differ
diff --git a/extensions/git-extended/src/commitsProvider.ts b/extensions/git-extended/src/commitsProvider.ts
new file mode 100644
index 00000000000..fa27f122747
--- /dev/null
+++ b/extensions/git-extended/src/commitsProvider.ts
@@ -0,0 +1,219 @@
+import * as path from 'path';
+import * as vscode from 'vscode';
+import { Repository } from './common//models/repository';
+import { getChangedFiles, getFile } from './common/file';
+import { getCommits } from './common/log';
+import { Commit } from './common/models/commit';
+import { GitChangeType } from './common/models/file';
+
+export class CommitTreeItem implements vscode.TreeItem {
+ readonly iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
+
+ constructor(
+ public readonly context: vscode.ExtensionContext,
+ public readonly label: string,
+ public readonly sha: string,
+ public readonly parentSHAs: ReadonlyArray,
+ public readonly command?: vscode.Command,
+ public readonly collapsibleState?: vscode.TreeItemCollapsibleState
+ ) {
+ this.collapsibleState = 1;
+ }
+}
+
+export class FileChangeItem implements vscode.TreeItem {
+ public iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
+ public filePath: string;
+ public sha: string;
+ public parentFilePath: string;
+ public parentSha: string;
+ public command?: vscode.Command;
+ public comments?: any[];
+
+ constructor(
+ public readonly label: string,
+ public readonly status: GitChangeType,
+ public readonly context: vscode.ExtensionContext,
+ public readonly fileName: string,
+ public readonly workspaceRoot?: string
+ ) {
+ }
+
+ public populateCommandArgs() {
+ if (this.status === GitChangeType.MODIFY) {
+ this.command = {
+ title: 'show diff',
+ command: ShowDiffCommand.id,
+ arguments: [this]
+ };
+ } else if (this.status === GitChangeType.DELETE) {
+ this.command = {
+ title: 'show diff',
+ command: 'vscode.open',
+ arguments: [
+ vscode.Uri.file(path.resolve(this.workspaceRoot, this.parentFilePath))
+ ]
+ };
+ } else {
+ this.command = {
+ title: 'show diff',
+ command: 'vscode.open',
+ arguments: [
+ vscode.Uri.file(path.resolve(this.workspaceRoot, this.filePath))
+ ]
+ };
+ }
+ }
+}
+
+class ShowDiffCommand {
+ static readonly id = 'msgit.showDiff';
+
+ static run(item: FileChangeItem) {
+ vscode.commands.executeCommand('vscode.diff',
+ vscode.Uri.file(path.resolve(item.workspaceRoot, item.parentFilePath)),
+ vscode.Uri.file(path.resolve(item.workspaceRoot, item.filePath)),
+ item.fileName);
+ }
+}
+
+export class CommitsProvider implements vscode.TreeDataProvider {
+ private context: vscode.ExtensionContext;
+ private workspaceRoot: string;
+ private repository: Repository;
+ private icons: any;
+ private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter();
+ readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event;
+
+ activate(context: vscode.ExtensionContext, workspaceRoot: string, repository: Repository) {
+ this.context = context;
+ this.workspaceRoot = workspaceRoot;
+ this.repository = repository;
+
+ vscode.window.registerTreeDataProvider('commits', this);
+ this.icons = {
+ light: {
+ Modified: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-modified.svg')),
+ Added: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-added.svg')),
+ Deleted: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-deleted.svg')),
+ Renamed: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-renamed.svg')),
+ Copied: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-copied.svg')),
+ Untracked: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-untrackedt.svg')),
+ Ignored: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-ignored.svg')),
+ Conflict: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-conflict.svg')),
+ },
+ dark: {
+ Modified: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-modified.svg')),
+ Added: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-added.svg')),
+ Deleted: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-deleted.svg')),
+ Renamed: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-renamed.svg')),
+ Copied: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-copied.svg')),
+ Untracked: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-untracked.svg')),
+ Ignored: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-ignored.svg')),
+ Conflict: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-conflict.svg'))
+ }
+ };
+
+ vscode.commands.registerCommand('commits.refresh', async (args) => {
+ this._onDidChangeTreeData.fire();
+ });
+ vscode.commands.registerCommand('commits.revertCommit', async (element) => {
+ // TODO
+ // We can want to allow users to revert several commits
+ });
+ vscode.commands.registerCommand(ShowDiffCommand.id, ShowDiffCommand.run);
+ }
+
+ getTreeItem(element: CommitTreeItem | FileChangeItem): vscode.TreeItem {
+ if (element instanceof FileChangeItem) {
+ let iconUri: string;
+ let iconDarkUri: string;
+
+ switch (element.status) {
+ case GitChangeType.ADD:
+ iconUri = this.icons.light.Added;
+ iconDarkUri = this.icons.dark.Added;
+ break;
+ case GitChangeType.COPY:
+ iconUri = this.icons.light.Copied;
+ iconDarkUri = this.icons.dark.Copied;
+ break;
+ case GitChangeType.DELETE:
+ iconUri = this.icons.light.Deleted;
+ iconDarkUri = this.icons.dark.Deleted;
+ break;
+ case GitChangeType.MODIFY:
+ iconUri = this.icons.light.Modified;
+ iconDarkUri = this.icons.dark.Modified;
+ break;
+ case GitChangeType.RENAME:
+ iconUri = this.icons.light.Renamed;
+ iconDarkUri = this.icons.dark.Renamed;
+ break;
+ }
+ element.iconPath = {
+ light: iconUri,
+ dark: iconDarkUri
+ };
+ return element;
+ } else {
+ return element;
+ }
+ }
+
+ getChildren(element?: CommitTreeItem): Thenable {
+ if (!this.workspaceRoot) {
+ return Promise.resolve([]);
+ }
+
+ return new Promise(resolve => {
+ if (element) {
+ getChangedFiles(this.repository, element.sha).then(fileChanges => {
+ let promises = [];
+ let results = fileChanges.map(fileChange => {
+ let changedItem = new FileChangeItem(`${fileChange.filePath}`, fileChange.status, this.context, this.workspaceRoot);
+
+ if (fileChange.status === GitChangeType.MODIFY) {
+ promises.push(Promise.all([getFile(element.sha, fileChange.filePath).then(targetFile => {
+ changedItem.filePath = targetFile;
+ changedItem.sha = element.sha;
+ }), getFile(element.parentSHAs[0], fileChange.filePath).then(targetFile => {
+ changedItem.parentFilePath = targetFile;
+ changedItem.parentSha = element.parentSHAs[0];
+ })]).then(() => {
+ changedItem.populateCommandArgs();
+ }));
+ } else if (fileChange.status === GitChangeType.DELETE) {
+ promises.push(getFile(element.parentSHAs[0], fileChange.filePath).then(targetFile => {
+ changedItem.parentFilePath = targetFile;
+ changedItem.sha = element.parentSHAs[0];
+ changedItem.populateCommandArgs();
+ }));
+ } else {
+ promises.push(getFile(element.sha, fileChange.filePath).then(targetFile => {
+ changedItem.filePath = targetFile;
+ changedItem.sha = element.sha;
+ changedItem.populateCommandArgs();
+ }));
+ }
+
+ return changedItem;
+ });
+
+ Promise.all(promises).then(() => {
+ resolve(results as any); // TODO
+ });
+ });
+ } else {
+ getCommits(this.repository, 'HEAD', 100).then((commits: Commit[]) => {
+ let result = commits.map(commit => {
+ return new CommitTreeItem(this.context, `${commit.summary}`, commit.sha, commit.parentSHAs);
+ });
+ resolve(result);
+ }, (reason: any) => {
+ Promise.reject(reason);
+ });
+ }
+ });
+ }
+}
diff --git a/extensions/git-extended/src/common/diff.ts b/extensions/git-extended/src/common/diff.ts
new file mode 100644
index 00000000000..8df56f3268a
--- /dev/null
+++ b/extensions/git-extended/src/common/diff.ts
@@ -0,0 +1,155 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as path from 'path';
+import { getFileContent, writeTmpFile } from './file';
+import { GitChangeType, RichFileChange } from './models/file';
+import { Repository } from './models/repository';
+
+export const MODIFY_DIFF_INFO = /diff --git a\/(\S+) b\/(\S+).*\n*index.*\n*-{3}.*\n*\+{3}.*\n*((.*\n*)+)/;
+export const NEW_FILE_INFO = /diff --git a\/(\S+) b\/(\S+).*\n*new file mode .*\nindex.*\n*-{3}.*\n*\+{3}.*\n*((.*\n*)+)/;
+export const DELETE_FILE_INFO = /diff --git a\/(\S+) b\/(\S+).*\n*deleted file mode .*\nindex.*\n*-{3}.*\n*\+{3}.*\n*((.*\n*)+)/;
+export const DIFF_HUNK_INFO = /@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@/;
+
+
+async function parseModifiedHunkComplete(originalContent, modifyDiffInfo, a, b) {
+ let left = originalContent.split(/\r|\n|\r\n/);
+ let diffHunks = modifyDiffInfo[3].split('\n');
+ diffHunks.pop(); // there is one additional line break at the end of the diff ??
+
+ let right = [];
+ let lastCommonLine = 0;
+ for (let i = 0; i < diffHunks.length; i++) {
+ let line = diffHunks[i];
+ if (DIFF_HUNK_INFO.test(line)) {
+ let changeInfo = DIFF_HUNK_INFO.exec(line);
+ let oriStartLine = Number(changeInfo[1]);
+ let oriEndLine = Number(changeInfo[3]) | 0;
+
+ for (let j = lastCommonLine + 1; j < oriStartLine; j++) {
+ right.push(left[j - 1]);
+ }
+ lastCommonLine = oriStartLine + oriEndLine - 1;
+ } else if (/^\+/.test(line)) {
+ right.push(line.substr(1));
+ } else {
+ let codeInFirstLine = line.substr(1);
+ right.push(codeInFirstLine);
+ }
+ }
+
+ if (lastCommonLine < left.length) {
+ for (let j = lastCommonLine + 1; j <= left.length; j++) {
+ right.push(left[j - 1]);
+ }
+ }
+
+ let contentPath = await writeTmpFile(right.join('\n'), path.extname(b));
+ let originalContentPath = await writeTmpFile(left.join('\n'), path.extname(a));
+
+ return new RichFileChange(contentPath, originalContentPath, GitChangeType.MODIFY, b);
+}
+
+async function parseModifiedHunkFast(modifyDiffInfo, a, b) {
+ let left = [];
+ let right = [];
+
+ let diffHunks = modifyDiffInfo[3].split('\n');
+ diffHunks.pop(); // there is one additional line break at the end of the diff ??
+
+ for (let i = 0; i < diffHunks.length; i++) {
+ let line = diffHunks[i];
+ if (/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@/.test(line)) {
+ // let changeInfo = /@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@/.exec(line);
+ left.push(line);
+ right.push(line);
+ } else if (/^\-/.test(line)) {
+ left.push(line.substr(1));
+ } else if (/^\+/.test(line)) {
+ right.push(line.substr(1));
+ } else {
+ let codeInFirstLine = line.substr(1);
+ left.push(codeInFirstLine);
+ right.push(codeInFirstLine);
+ }
+ }
+
+ let contentPath = await writeTmpFile(right.join('\n'), path.extname(b));
+ let originalContentPath = await writeTmpFile(left.join('\n'), path.extname(a));
+
+ return new RichFileChange(contentPath, originalContentPath, GitChangeType.MODIFY, b);
+}
+
+export async function parseDiff(text: string, repository: Repository, parentCommit: string) {
+ let reg = /diff((?!diff).*\n*)*/g;
+ let match = reg.exec(text);
+ let richFileChanges: RichFileChange[] = [];
+
+ while(match) {
+ let singleFileDiff = match[0];
+ let modifyDiffInfo = MODIFY_DIFF_INFO.exec(singleFileDiff);
+ if (modifyDiffInfo) {
+ let a = modifyDiffInfo[1];
+ let b = modifyDiffInfo[2];
+
+ try {
+ let originalContent = await getFileContent(repository.path, parentCommit, a);
+ let richFileChange = await parseModifiedHunkComplete(originalContent, modifyDiffInfo, a, b);
+ richFileChanges.push(richFileChange);
+ } catch (e) {
+ let richFileChange = await parseModifiedHunkFast(modifyDiffInfo, a, b);
+ richFileChanges.push(richFileChange);
+ }
+
+ match = reg.exec(text);
+ continue;
+ }
+
+ let newDiffInfo = NEW_FILE_INFO.exec(singleFileDiff);
+ if (newDiffInfo) {
+ let fileName = newDiffInfo[1];
+ let diffHunks = newDiffInfo[3].split('\n');
+ let contentArray = [];
+ for (let i = 0; i < diffHunks.length; i++) {
+ if (/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@$/.test(diffHunks[i])) {
+ continue;
+ } else if (/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@ /.test(diffHunks[i])) {
+ contentArray.push(diffHunks[i].replace(/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@ /, ''));
+ } else if (/^\+/.test(diffHunks[i])) {
+ contentArray.push(diffHunks[i].substr(1));
+ }
+ }
+ let filePath = await writeTmpFile(contentArray.join('\n'), path.extname(fileName));
+ let richFileChange = new RichFileChange(filePath, filePath, GitChangeType.ADD, fileName);
+ richFileChanges.push(richFileChange);
+ match = reg.exec(text);
+ continue;
+ }
+
+ let deleteDiffInfo = DELETE_FILE_INFO.exec(singleFileDiff);
+ if (deleteDiffInfo) {
+ let fileName = deleteDiffInfo[1];
+ let diffHunks = deleteDiffInfo[3].split('\n');
+ let contentArray = [];
+ for (let i = 0; i < diffHunks.length; i++) {
+ if (/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@$/.test(diffHunks[i])) {
+ continue;
+ } else if (/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@ /.test(diffHunks[i])) {
+ contentArray.push(diffHunks[i].replace(/@@ \-(\d+)(,(\d+))?( \+(\d+)(,(\d+)?))? @@ /, ''));
+ } else if (/^\-/.test(diffHunks[i])) {
+ contentArray.push(diffHunks[i].substr(1));
+ }
+ }
+ let originalFilePath = await writeTmpFile(contentArray.join('\n'), path.extname(fileName));
+ let filePath = await writeTmpFile('', path.extname(fileName));
+ let richFileChange = new RichFileChange(filePath, originalFilePath, GitChangeType.DELETE, fileName);
+ richFileChanges.push(richFileChange);
+ match = reg.exec(text);
+ continue;
+ }
+ match = reg.exec(text);
+ }
+
+ return richFileChanges;
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/file.ts b/extensions/git-extended/src/common/file.ts
new file mode 100644
index 00000000000..b6405168628
--- /dev/null
+++ b/extensions/git-extended/src/common/file.ts
@@ -0,0 +1,107 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as tmp from 'tmp';
+import * as vscode from 'vscode';
+import { GitProcess } from 'dugite';
+import { Repository } from './models/repository';
+import { SlimFileChange, GitChangeType, fromStatus } from './models/file';
+
+
+/**
+ * @param content
+ *
+ * a
+ *
+ * c
+ *
+ * b
+ *
+ *
+ * @param ext
+ */
+
+export async function writeTmpFile(content: string, ext: string): Promise {
+ return new Promise((resolve, reject) => {
+ tmp.file({ postfix: ext }, async (err: any, tmpFilePath: string) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+
+ try {
+ fs.appendFileSync(tmpFilePath, content);
+ resolve(tmpFilePath);
+ } catch (ex) {
+ reject(ex);
+ }
+ });
+ })
+}
+
+export async function getFile(commitSha1: string, localFilePath: string): Promise {
+ const rootDir = vscode.workspace.rootPath;
+ return new Promise((resolve, reject) => {
+ if (commitSha1 === undefined) {
+ resolve('fileUnavailable');
+ return;
+ }
+ let ext = path.extname(localFilePath);
+ tmp.file({ postfix: ext }, async (err: any, tmpFilePath: string) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ try {
+ let data = await getFileContent(rootDir, commitSha1, localFilePath);
+ fs.appendFileSync(tmpFilePath, data);
+ resolve(tmpFilePath);
+ }
+ catch (ex) {
+ console.log(ex);
+ reject(ex);
+ }
+ });
+ });
+}
+
+export async function getFileContent(rootDir: string, commitSha: string, sourceFilePath: string): Promise {
+ const result = await GitProcess.exec([
+ 'show',
+ `${commitSha}:` + sourceFilePath.replace(/\\/g, '/')
+ ], rootDir);
+
+ const out = result.stdout;
+ const error = result.stderr;
+
+ if (result.exitCode === 0) {
+ return out;
+ } else {
+ throw error;
+ }
+}
+
+export async function getChangedFiles(repository: Repository, sha: string): Promise> {
+ const args = ['log', sha, '--name-status', '--format=format:', '-z', '-1'];
+ const result = await GitProcess.exec(args, repository.path);
+
+ const out = result.stdout;
+ const lines = out.split('\0');
+ lines.splice(-1, 1);
+
+ const files: SlimFileChange[] = [];
+ for (let i = 0; i < lines.length; i++) {
+ const statusText = lines[i];
+ const status = fromStatus(statusText);
+ let originalPath: string | undefined = undefined;
+
+ if (status === GitChangeType.RENAME || status === GitChangeType.COPY) {
+ originalPath = lines[++i];
+ }
+
+ const path = lines[++i];
+
+ files.push(new SlimFileChange(path, originalPath, status, null));
+ }
+
+ return files;
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/log.ts b/extensions/git-extended/src/common/log.ts
new file mode 100644
index 00000000000..32352dc53fd
--- /dev/null
+++ b/extensions/git-extended/src/common/log.ts
@@ -0,0 +1,77 @@
+import { Repository } from './models/repository';
+import { Commit } from './models/commit';
+import { GitProcess } from 'dugite';
+
+export async function getParentCommit(repository: Repository, sha: string): Promise {
+ const result = await GitProcess.exec(
+ [
+ 'rev-list',
+ '--parents',
+ '-n',
+ '1',
+ sha
+ ],
+ repository.path
+ );
+ let commits = result.stdout.split(' ');
+ if (commits.length > 2) {
+ return commits[1];
+ } else {
+ return null;
+ }
+}
+
+
+
+export async function getCommits(repository: Repository, revisionRange: string, limit: number, additionalArgs: ReadonlyArray = []): Promise {
+ const delimiter = '1F';
+ const delimiterString = String.fromCharCode(parseInt(delimiter, 16));
+ const prettyFormat = [
+ '%H', // SHA
+ '%s', // summary
+ '%P', // parent SHAs
+ ].join(`%x${delimiter}`);
+
+ const result = await GitProcess.exec([
+ 'log',
+ revisionRange,
+ `--max-count=${limit}`,
+ `--pretty=${prettyFormat}`,
+ '-z',
+ ...additionalArgs,
+ ], repository.path);
+
+ const out = result.stdout;
+ const lines = out.split('\0');
+ lines.splice(-1, 1);
+
+ const commits = lines.map(line => {
+ const pieces = line.split(delimiterString);
+ const sha = pieces[0];
+ const summary = pieces[1];
+ const shaList = pieces[2];
+ const parentSHAs = shaList.length ? shaList.split(' ') : [];
+
+ return new Commit(sha, summary, parentSHAs);
+ });
+
+ return commits;
+}
+
+export async function isWorkingTreeClean(repository: Repository): Promise {
+ const result = await GitProcess.exec(
+ [
+ 'diff-index',
+ '--quiet',
+ 'HEAD',
+ '--'
+ ],
+ repository.path
+ );
+ let exitCode = result.exitCode;
+ if (exitCode !== 0) {
+ return false;
+ } else {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/models/comment.ts b/extensions/git-extended/src/common/models/comment.ts
new file mode 100644
index 00000000000..1654f0549b5
--- /dev/null
+++ b/extensions/git-extended/src/common/models/comment.ts
@@ -0,0 +1,29 @@
+interface DiffHunkRange {
+ originalStart: number;
+ originalLength: number;
+ start: number;
+ length: number;
+
+}
+
+interface User {
+ id: string;
+ login: string;
+}
+export interface Comment {
+ url: string;
+ id: string;
+ path: string;
+ pull_request_review_id: string;
+ diff_hunk_range: DiffHunkRange;
+ position: number;
+ originalPosition: number;
+ commit_id: string;
+ original_commit_id: string;
+ user: User;
+ body: string;
+ created_at: string;
+ updated_at: string;
+ html_url: string;
+}
+
diff --git a/extensions/git-extended/src/common/models/commit.ts b/extensions/git-extended/src/common/models/commit.ts
new file mode 100644
index 00000000000..ce83eda5b96
--- /dev/null
+++ b/extensions/git-extended/src/common/models/commit.ts
@@ -0,0 +1,3 @@
+export class Commit {
+ constructor(public sha: string, public summary: string, public parentSHAs: string[]) { }
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/models/file.ts b/extensions/git-extended/src/common/models/file.ts
new file mode 100644
index 00000000000..4e38d8138a1
--- /dev/null
+++ b/extensions/git-extended/src/common/models/file.ts
@@ -0,0 +1,49 @@
+export enum GitChangeType {
+ ADD,
+ COPY,
+ DELETE,
+ MODIFY,
+ RENAME,
+ TYPE,
+ UNKNOWN,
+ UNMERGED
+}
+
+export function fromStatus(status: string): GitChangeType {
+ switch (status) {
+ case 'A': return GitChangeType.ADD;
+ case 'C': return GitChangeType.COPY;
+ case 'D': return GitChangeType.DELETE;
+ case 'M': return GitChangeType.MODIFY;
+ case 'R': return GitChangeType.RENAME;
+ case 'T': return GitChangeType.TYPE;
+ case 'X': return GitChangeType.UNKNOWN;
+ case 'U': return GitChangeType.UNMERGED;
+ }
+
+ if (status.match(/R[0-9]+/)) { return GitChangeType.RENAME; }
+ if (status.match(/C[0-9]+/)) { return GitChangeType.COPY; }
+
+ return GitChangeType.MODIFY;
+}
+
+export class SlimFileChange {
+ public originalContent: string;
+ public content: string;
+
+ constructor(
+ public readonly filePath: string,
+ public readonly originalFilePath: string,
+ public readonly status: GitChangeType,
+ public readonly fileName: string
+ ) { }
+}
+
+export class RichFileChange {
+ constructor(
+ public readonly filePath: string,
+ public readonly originalFilePath: string,
+ public readonly status: GitChangeType,
+ public readonly fileName: string
+ ) { }
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/models/remote.ts b/extensions/git-extended/src/common/models/remote.ts
new file mode 100644
index 00000000000..5b21e704485
--- /dev/null
+++ b/extensions/git-extended/src/common/models/remote.ts
@@ -0,0 +1,21 @@
+export interface IRemote {
+ readonly name: string;
+ readonly url: string;
+}
+
+export interface IGitRemoteURL {
+ /** The hostname of the remote. */
+ readonly hostname: string;
+
+ /**
+ * The owner of the GitHub repository. This will be null if the URL doesn't
+ * take the form of a GitHub repository URL (e.g., owner/name).
+ */
+ readonly owner: string | null;
+
+ /**
+ * The name of the GitHub repository. This will be null if the URL doesn't
+ * take the form of a GitHub repository URL (e.g., owner/name).
+ */
+ readonly name: string | null;
+}
diff --git a/extensions/git-extended/src/common/models/repository.ts b/extensions/git-extended/src/common/models/repository.ts
new file mode 100644
index 00000000000..e50b1feadd1
--- /dev/null
+++ b/extensions/git-extended/src/common/models/repository.ts
@@ -0,0 +1,19 @@
+import { IGitRemoteURL } from './remote';
+
+export class GitHubRepository {
+ constructor(
+ public name: string,
+ public owner: string,
+ public url: string
+ ) { }
+}
+export class Repository {
+ public path: string;
+
+ public remotes: IGitRemoteURL[];
+
+ constructor(path: string, remotes: IGitRemoteURL[]) {
+ this.path = path;
+ this.remotes = remotes;
+ }
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/common/remote.ts b/extensions/git-extended/src/common/remote.ts
new file mode 100644
index 00000000000..eb17acfde14
--- /dev/null
+++ b/extensions/git-extended/src/common/remote.ts
@@ -0,0 +1,47 @@
+import { GitProcess } from 'dugite';
+import { IGitRemoteURL } from './models/remote';
+
+export async function getRemotes(
+ path: string
+) {
+ const result = await GitProcess.exec(['remote', '-v'], path);
+ const output = result.stdout;
+ const lines = output.split('\n');
+ const remotes = lines
+ .filter(x => x.endsWith('(fetch)'))
+ .map(x => x.split(/\s+/))
+ .map(x => ({ name: x[0], url: x[1] }));
+
+ return remotes;
+}
+
+/** Parse the remote information from URL. */
+export function parseRemote(url: string): IGitRemoteURL | null {
+ // Examples:
+ // https://github.com/octocat/Hello-World.git
+ // https://github.com/octocat/Hello-World.git/
+ // git@github.com:octocat/Hello-World.git
+ // git:github.com/octocat/Hello-World.git
+ const regexes = [
+ new RegExp('^https?://(?:.+@)?(.+)/(.+)/(.+?)(?:/|.git/?)?$'),
+ new RegExp('^git@(.+):(.+)/(.+?)(?:/|.git)?$'),
+ new RegExp('^git:(.+)/(.+)/(.+?)(?:/|.git)?$'),
+ new RegExp('^ssh://git@(.+)/(.+)/(.+?)(?:/|.git)?$')
+ ];
+
+ for (const regex of regexes) {
+ const result = url.match(regex);
+ if (!result) {
+ continue;
+ }
+
+ const hostname = result[1];
+ const owner = result[2];
+ const name = result[3];
+ if (hostname) {
+ return { hostname, owner, name };
+ }
+ }
+
+ return null;
+}
diff --git a/extensions/git-extended/src/extension.ts b/extensions/git-extended/src/extension.ts
new file mode 100644
index 00000000000..4ba37bb9077
--- /dev/null
+++ b/extensions/git-extended/src/extension.ts
@@ -0,0 +1,20 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import * as vscode from 'vscode';
+import { CommitsProvider } from './commitsProvider';
+import { PRProvider } from './prProvider';
+import { Repository } from './common/models/repository';
+import { getRemotes, parseRemote } from './common/remote';
+
+export async function activate(context: vscode.ExtensionContext) {
+ const rootPath = vscode.workspace.rootPath;
+ const remotes = await getRemotes(rootPath);
+ const remoteUrls = remotes.map(remote => parseRemote(remote.url));
+ const repository = new Repository(rootPath, remoteUrls);
+ new CommitsProvider().activate(context, rootPath, repository);
+ new PRProvider().activate(context, rootPath, repository);
+}
\ No newline at end of file
diff --git a/extensions/git-extended/src/prProvider.ts b/extensions/git-extended/src/prProvider.ts
new file mode 100644
index 00000000000..4be162bd475
--- /dev/null
+++ b/extensions/git-extended/src/prProvider.ts
@@ -0,0 +1,242 @@
+import * as vscode from 'vscode';
+// import { Octokat } from 'octokat';
+var Octokat = require('octokat');
+// import * as https from 'https';
+// import * as fs from 'fs';
+// import * as tmp from 'tmp';
+import * as path from 'path';
+import * as request from 'request';
+
+import { FileChangeItem } from './commitsProvider';
+import { parseDiff, DIFF_HUNK_INFO } from './common/diff';
+import { GitChangeType } from './common/models/file';
+import { Repository } from './common//models/repository';
+import { Comment } from './common/models/comment';
+import * as _ from 'lodash';
+export class PullRequest {
+ constructor(public octoPRItem: any) { };
+}
+
+export class PRProvider implements vscode.TreeDataProvider, vscode.CommentProvider {
+ private _fileChanges: FileChangeItem[];
+ private _comments?: Comment[];
+ private context: vscode.ExtensionContext;
+ private workspaceRoot: string;
+ private repository: Repository;
+ private octo: any;
+ private icons: any;
+
+ constructor() {
+ this.octo = new Octokat();
+ }
+
+ activate(context: vscode.ExtensionContext, workspaceRoot: string, repository: Repository) {
+ this.context = context;
+ this.workspaceRoot = workspaceRoot;
+ this.repository = repository;
+
+ vscode.window.registerTreeDataProvider('pr', this);
+ this.icons = {
+ light: {
+ Modified: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-modified.svg')),
+ Added: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-added.svg')),
+ Deleted: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-deleted.svg')),
+ Renamed: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-renamed.svg')),
+ Copied: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-copied.svg')),
+ Untracked: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-untrackedt.svg')),
+ Ignored: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-ignored.svg')),
+ Conflict: context.asAbsolutePath(path.join('resources', 'icons', 'light', 'status-conflict.svg')),
+ },
+ dark: {
+ Modified: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-modified.svg')),
+ Added: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-added.svg')),
+ Deleted: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-deleted.svg')),
+ Renamed: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-renamed.svg')),
+ Copied: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-copied.svg')),
+ Untracked: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-untracked.svg')),
+ Ignored: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-ignored.svg')),
+ Conflict: context.asAbsolutePath(path.join('resources', 'icons', 'dark', 'status-conflict.svg'))
+ }
+ };
+
+ vscode.workspace.registerCommentProvider(this);
+ }
+
+ getTreeItem(element: PullRequest | FileChangeItem): vscode.TreeItem {
+
+ if (element instanceof PullRequest) {
+ return {
+ label: element.octoPRItem.title,
+ collapsibleState: 1
+ };
+ } else {
+ let iconUri: string;
+ let iconDarkUri: string;
+
+ switch (element.status) {
+ case GitChangeType.ADD:
+ iconUri = this.icons.light.Added;
+ iconDarkUri = this.icons.dark.Added;
+ break;
+ case GitChangeType.COPY:
+ iconUri = this.icons.light.Copied;
+ iconDarkUri = this.icons.dark.Copied;
+ break;
+ case GitChangeType.DELETE:
+ iconUri = this.icons.light.Deleted;
+ iconDarkUri = this.icons.dark.Deleted;
+ break;
+ case GitChangeType.MODIFY:
+ iconUri = this.icons.light.Modified;
+ iconDarkUri = this.icons.dark.Modified;
+ break;
+ case GitChangeType.RENAME:
+ iconUri = this.icons.light.Renamed;
+ iconDarkUri = this.icons.dark.Renamed;
+ break;
+ }
+ element.iconPath = {
+ light: iconUri,
+ dark: iconDarkUri
+ };
+ return element;
+ }
+ }
+
+ getChildren(element?: PullRequest): PullRequest[] | Thenable | FileChangeItem[] | Thenable {
+ if (element) {
+ return new Promise((resolve, rxeject) => {
+ request({
+ followAllRedirects: true,
+ url: element.octoPRItem.diffUrl
+ }, async (error, response, body) => {
+ // map comments to FileChangeItem
+ // registerCommentProvider
+ const rawComments = await element.octoPRItem.reviewComments.fetch();
+ const comments: Comment[] = parseComments(rawComments.items);
+ let richContentChanges = await parseDiff(body, this.repository, element.octoPRItem.base.sha);
+ let fileChanges = richContentChanges.map(change => {
+ let changedItem = new FileChangeItem(change.fileName ? change.fileName : change.filePath, change.status, this.context, change.fileName, this.workspaceRoot);
+ changedItem.filePath = change.filePath;
+ changedItem.parentFilePath = change.originalFilePath;
+ changedItem.populateCommandArgs();
+ return changedItem;
+ });
+ this._fileChanges = fileChanges;
+ this._comments = comments;
+
+ vscode.workspace.onDidOpenTextDocument(e => {
+ let matchingComments = getMatchingCommentsForDiffViewEditor(e.fileName, fileChanges, comments);
+ console.log('---diff editor---')
+ for (let i = 0; i < matchingComments.length; i++) {
+ let cm = matchingComments[i];
+ console.log(`after line: ${cm.diff_hunk_range.start + cm.position - 1 - 1}, ${'@' + cm.user.login}: '${cm.body}'`);
+ }
+
+ matchingComments = getMatchingCommentsForNormalEditor(e.fileName, this.workspaceRoot, comments);
+ console.log('---editor---')
+ for (let i = 0; i < matchingComments.length; i++) {
+ let cm = matchingComments[i];
+ console.log(`after line: ${cm.diff_hunk_range.start + cm.position - 1 - 1}, ${'@' + cm.user.login}: '${cm.body}'`);
+ }
+ });
+
+ resolve(fileChanges);
+ });
+ });
+ } else {
+ if (this.repository.remotes && this.repository.remotes.length > 0) {
+ let promises = this.repository.remotes.map(remote => this.octo.repos(remote.owner, remote.name).pulls.fetch().then(prs => {
+ return prs.items.map(item => new PullRequest(item));
+ }));
+ return Promise.all(promises).then(values => {
+ let prs = [];
+ values.forEach(value => {
+ prs.push(...value);
+ });
+
+ return prs;
+ });
+ }
+
+ return [];
+ }
+ }
+
+ async provideComments(document: vscode.TextDocument): Promise {
+ if (!this._comments) {
+ return [];
+ }
+
+ let matchingComments = getMatchingCommentsForDiffViewEditor(document.fileName, this._fileChanges, this._comments);
+ if (!matchingComments || !matchingComments.length) {
+ matchingComments = getMatchingCommentsForNormalEditor(document.fileName, this.workspaceRoot, this._comments);
+ }
+
+ if (!matchingComments || !matchingComments.length) {
+ return [];
+ }
+
+ let sections = _.groupBy(matchingComments, comment => comment.position);
+ let ret = [];
+
+ for (let i in sections) {
+ let comments = sections[i];
+
+ const comment = comments[0];
+ const pos = new vscode.Position(comment.diff_hunk_range.start + comment.position - 1 - 1, 0);
+ const range = new vscode.Range(pos, pos);
+
+ ret.push({
+ range,
+ comments: comments.map(comment => {
+ return {
+ body: new vscode.MarkdownString(comment.body),
+ userName: comment.user.login
+ };
+ })
+ });
+ }
+
+ return ret;
+ }
+}
+
+function parseComments(comments: any[]): Comment[] {
+ for (let i = 0; i < comments.length; i++) {
+ let diff_hunk = comments[i].diffHunk;
+ let hunk_info = DIFF_HUNK_INFO.exec(diff_hunk);
+ let oriStartLine = Number(hunk_info[1]);
+ let oriLen = Number(hunk_info[3]) | 0;
+ let startLine = Number(hunk_info[5]);
+ let len = Number(hunk_info[7]) | 0;
+ comments[i].diff_hunk_range = {
+ originalStart: oriStartLine,
+ originalLength: oriLen,
+ start: startLine,
+ length: len
+ };
+ }
+
+ return comments;
+}
+
+function getMatchingCommentsForDiffViewEditor(filePath: string, items: FileChangeItem[], comments: Comment[]): Comment[] {
+ let fileChangeItem = items.filter(item => filePath === path.resolve(item.workspaceRoot, item.filePath));
+
+ if (fileChangeItem.length === 0) {
+ return [];
+ } else {
+ let fileName = fileChangeItem[0].fileName;
+ let matchingComments = comments.filter(comment => comment.path === fileName);
+
+ return matchingComments;
+ }
+}
+
+function getMatchingCommentsForNormalEditor(filePath: string, workspaceRoot: string, comments: Comment[]): Comment[] {
+ // @todo, we should check commit id
+ let matchingComments = comments.filter(comment => path.resolve(workspaceRoot, comment.path) === filePath);
+ return matchingComments;
+}
+
diff --git a/extensions/git-extended/src/typings/ref.d.ts b/extensions/git-extended/src/typings/ref.d.ts
new file mode 100644
index 00000000000..c9849d48e08
--- /dev/null
+++ b/extensions/git-extended/src/typings/ref.d.ts
@@ -0,0 +1,7 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+///
+///
diff --git a/extensions/git-extended/tsconfig.json b/extensions/git-extended/tsconfig.json
new file mode 100644
index 00000000000..9508ff037b1
--- /dev/null
+++ b/extensions/git-extended/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "outDir": "out",
+ "noUnusedLocals": true,
+ "lib": [
+ "es6"
+ ],
+ "sourceMap": true,
+ "rootDir": "./src",
+ "jsx": "react"
+ },
+ "exclude": [
+ "node_modules",
+ ".vscode-test"
+ ]
+}
diff --git a/extensions/git-extended/yarn.lock b/extensions/git-extended/yarn.lock
new file mode 100644
index 00000000000..fbf82ecae16
--- /dev/null
+++ b/extensions/git-extended/yarn.lock
@@ -0,0 +1,482 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/node@*":
+ version "9.6.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.2.tgz#e49ac1adb458835e95ca6487bc20f916b37aff23"
+
+ajv@^5.1.0:
+ version "5.5.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
+ dependencies:
+ co "^4.6.0"
+ fast-deep-equal "^1.0.0"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.3.0"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@1.0.0, assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+
+aws-sign2@~0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
+
+aws4@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+boom@4.x.x:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
+ dependencies:
+ hoek "4.x.x"
+
+boom@5.x.x:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
+ dependencies:
+ hoek "4.x.x"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+caseless@~0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
+
+checksum@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9"
+ dependencies:
+ optimist "~0.3.5"
+
+chownr@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+
+combined-stream@1.0.6, combined-stream@~1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+
+core-util-is@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cryptiles@3.x.x:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
+ dependencies:
+ boom "5.x.x"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+dugite@^1.28.0:
+ version "1.61.0"
+ resolved "https://registry.yarnpkg.com/dugite/-/dugite-1.61.0.tgz#d5dad047af478c1312f8b791eb56d9935e6fc47d"
+ dependencies:
+ checksum "^0.1.1"
+ mkdirp "^0.5.1"
+ progress "^2.0.0"
+ request "^2.83.0"
+ rimraf "^2.5.4"
+ tar "^4.0.2"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+extend@~3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
+
+extsprintf@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
+
+extsprintf@^1.2.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
+
+fast-deep-equal@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~2.3.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099"
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "1.0.6"
+ mime-types "^2.1.12"
+
+fs-minipass@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d"
+ dependencies:
+ minipass "^2.2.1"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+
+getpass@^0.1.1:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
+ dependencies:
+ assert-plus "^1.0.0"
+
+glob@^7.0.5:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+har-schema@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
+
+har-validator@~5.0.3:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
+ dependencies:
+ ajv "^5.1.0"
+ har-schema "^2.0.0"
+
+hawk@~6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ sntp "2.x.x"
+
+hoek@4.x.x:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
+
+http-signature@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
+ dependencies:
+ assert-plus "^1.0.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+json-schema-traverse@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+jsprim@^1.2.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
+ dependencies:
+ assert-plus "1.0.0"
+ extsprintf "1.3.0"
+ json-schema "0.2.3"
+ verror "1.10.0"
+
+lodash@4.17.5, lodash@^4.16.4:
+ version "4.17.5"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
+
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+
+mime-types@^2.1.12, mime-types@~2.1.17:
+ version "2.1.18"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ dependencies:
+ mime-db "~1.33.0"
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@0.0.8:
+ version "0.0.8"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+minipass@^2.2.1, minipass@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.4.tgz#03c824d84551ec38a8d1bb5bc350a5a30a354a40"
+ dependencies:
+ safe-buffer "^5.1.1"
+ yallist "^3.0.0"
+
+minizlib@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb"
+ dependencies:
+ minipass "^2.2.1"
+
+mkdirp@^0.5.0, mkdirp@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
+ dependencies:
+ minimist "0.0.8"
+
+oauth-sign@~0.8.2:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+octokat@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/octokat/-/octokat-0.8.0.tgz#50841ca255743f91a715d11a1bde76f2eefcd97a"
+ dependencies:
+ lodash "^4.16.4"
+ xmlhttprequest "~1.8.0"
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+optimist@~0.3.5:
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9"
+ dependencies:
+ wordwrap "~0.0.2"
+
+os-tmpdir@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+
+performance-now@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
+
+progress@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
+
+punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+qs@~6.5.1:
+ version "6.5.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
+
+request@^2.81.0, request@^2.83.0:
+ version "2.85.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa"
+ dependencies:
+ aws-sign2 "~0.7.0"
+ aws4 "^1.6.0"
+ caseless "~0.12.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.1"
+ forever-agent "~0.6.1"
+ form-data "~2.3.1"
+ har-validator "~5.0.3"
+ hawk "~6.0.2"
+ http-signature "~1.2.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.17"
+ oauth-sign "~0.8.2"
+ performance-now "^2.1.0"
+ qs "~6.5.1"
+ safe-buffer "^5.1.1"
+ stringstream "~0.0.5"
+ tough-cookie "~2.3.3"
+ tunnel-agent "^0.6.0"
+ uuid "^3.1.0"
+
+rimraf@^2.5.4:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ dependencies:
+ glob "^7.0.5"
+
+safe-buffer@^5.0.1, safe-buffer@^5.1.1:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
+
+sntp@2.x.x:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
+ dependencies:
+ hoek "4.x.x"
+
+sshpk@^1.7.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+stringstream@~0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+tar@^4.0.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.1.tgz#b25d5a8470c976fd7a9a8a350f42c59e9fa81749"
+ dependencies:
+ chownr "^1.0.1"
+ fs-minipass "^1.2.5"
+ minipass "^2.2.4"
+ minizlib "^1.1.0"
+ mkdirp "^0.5.0"
+ safe-buffer "^5.1.1"
+ yallist "^3.0.2"
+
+tmp@^0.0.31:
+ version "0.0.31"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
+ dependencies:
+ os-tmpdir "~1.0.1"
+
+tough-cookie@~2.3.3:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
+ dependencies:
+ punycode "^1.4.1"
+
+tunnel-agent@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
+ dependencies:
+ safe-buffer "^5.0.1"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+typescript@^2.1.4:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624"
+
+uuid@^3.1.0:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
+
+verror@1.10.0:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
+ dependencies:
+ assert-plus "^1.0.0"
+ core-util-is "1.0.2"
+ extsprintf "^1.2.0"
+
+wordwrap@~0.0.2:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+xmlhttprequest@~1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
+
+yallist@^3.0.0, yallist@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts
index 64bac6dd228..c77b0a7137b 100644
--- a/src/vs/editor/common/modes.ts
+++ b/src/vs/editor/common/modes.ts
@@ -936,6 +936,22 @@ export interface Command {
tooltip?: string;
arguments?: any[];
}
+
+
+export interface CommentThread {
+ readonly range: IRange;
+ readonly comments: Comment[];
+}
+
+export interface Comment {
+ readonly body: IMarkdownString;
+ readonly userName: string;
+}
+
+export interface CommentProvider {
+ provideComments(model: model.ITextModel, token: CancellationToken): CommentThread[];
+}
+
export interface ICodeLensSymbol {
range: IRange;
id?: string;
diff --git a/src/vs/editor/contrib/review/review.ts b/src/vs/editor/contrib/review/review.ts
index 1c410273003..5c0332d869f 100644
--- a/src/vs/editor/contrib/review/review.ts
+++ b/src/vs/editor/contrib/review/review.ts
@@ -5,6 +5,7 @@
'use strict';
import 'vs/css!./review';
+import * as modes from 'vs/editor/common/modes';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IViewZone } from 'vs/editor/browser/editorBrowser';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
@@ -13,10 +14,8 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { TrackedRangeStickiness } from 'vs/editor/common/model';
import { ZoneWidget, IOptions } from '../zoneWidget/zoneWidget';
-import { MarkdownString } from 'vs/base/common/htmlContent';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
-import { IComment, getComments } from 'vs/editor/contrib/review/reviewProvider';
-import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
@@ -67,7 +66,7 @@ export class ReviewZoneWidget extends ZoneWidget {
container.appendChild(this._domNode);
}
- display(comments: IComment[], lineNumber: number) {
+ display(comments: modes.Comment[], lineNumber: number) {
this.show({ lineNumber: lineNumber, column: 1 }, 2);
for (let i = 0; i < comments.length; i++) {
@@ -76,21 +75,21 @@ export class ReviewZoneWidget extends ZoneWidget {
let header = document.createElement('h4');
let author = document.createElement('strong');
author.className = 'author';
- author.innerText = comments[i].user;
- let time = document.createElement('span');
- time.className = 'created_at';
- time.innerText = comments[i].created_at;
+ author.innerText = comments[i].userName;
+ // let time = document.createElement('span');
+ // time.className = 'created_at';
+ // time.innerText = comments[i].created_at;
header.appendChild(author);
- header.appendChild(time);
+ // header.appendChild(time);
singleCommentContainer.appendChild(header);
let body = document.createElement('div');
body.className = 'comment-body';
singleCommentContainer.appendChild(body);
- let md = new MarkdownString(comments[i].body);
+ let md = comments[i].body;
body.appendChild(renderMarkdown(md));
this._domNode.appendChild(singleCommentContainer);
- // this._domNode.appendChild(document.createElement('textarea'));
}
+ // this._domNode.appendChild(document.createElement('textarea'));
this._resizeObserver = new ResizeObserver(entries => {
if (entries[0].target === this._domNode) {
const lineHeight = this.editor.getConfiguration().lineHeight;
@@ -111,7 +110,6 @@ export class ReviewZoneWidget extends ZoneWidget {
}
export class ReviewController implements IEditorContribution {
-
private globalToDispose: IDisposable[];
private localToDispose: IDisposable[];
private editor: ICodeEditor;
@@ -119,16 +117,18 @@ export class ReviewController implements IEditorContribution {
private _domNode: HTMLElement;
private _zoneWidget: ReviewZoneWidget;
private _reviewPanelVisible: IContextKey;
+ private _commentThreads: modes.CommentThread[];
constructor(
editor: ICodeEditor,
- @IContextKeyService contextKeyService: IContextKeyService,
+ @IContextKeyService contextKeyService: IContextKeyService
) {
this.editor = editor;
this.globalToDispose = [];
this.localToDispose = [];
this.decorationIDs = [];
this.mouseDownInfo = null;
+ this._commentThreads = [];
this._reviewPanelVisible = ctxReviewPanelVisible.bindTo(contextKeyService);
this._domNode = document.createElement('div');
@@ -161,20 +161,6 @@ export class ReviewController implements IEditorContribution {
this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
-
- this.editor.changeDecorations(accessor => {
- this.decorationIDs = accessor.deltaDecorations(this.decorationIDs, [
- {
- range: {
- startLineNumber: 6,
- startColumn: 1,
- endLineNumber: 6,
- endColumn: 1
- },
- options: REVIEWL_DECORATION
- }
- ]);
- });
}
private mouseDownInfo: { lineNumber: number, iconClicked: boolean };
@@ -232,9 +218,30 @@ export class ReviewController implements IEditorContribution {
this._reviewPanelVisible.set(true);
this._zoneWidget = new ReviewZoneWidget(this.editor);
- this._zoneWidget.display(getComments(), lineNumber);
+ this._zoneWidget.display(this.getComments(lineNumber), lineNumber);
}
+ getComments(line: number): modes.Comment[] {
+ for (let i = 0; i < this._commentThreads.length; i++) {
+ if (this._commentThreads[i].range.startLineNumber === line) {
+ return this._commentThreads[i].comments;
+ }
+ }
+
+ return [];
+ }
+
+ setComments(commentThreads: modes.CommentThread[]): void {
+ this._commentThreads = commentThreads;
+ this.editor.changeDecorations(accessor => {
+ this.decorationIDs = accessor.deltaDecorations(this.decorationIDs, commentThreads.map(thread => ({
+ range: thread.range,
+ options: REVIEWL_DECORATION
+ })));
+ });
+ }
+
+
public closeWidget(): void {
this._reviewPanelVisible.reset();
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index dd91585c691..2e8a7e01f79 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -4998,6 +4998,20 @@ declare namespace monaco.languages {
arguments?: any[];
}
+ export interface CommentThread {
+ readonly range: IRange;
+ readonly comments: Comment[];
+ }
+
+ export interface Comment {
+ readonly body: IMarkdownString;
+ readonly userName: string;
+ }
+
+ export interface CommentProvider {
+ provideComments(model: editor.ITextModel, token: CancellationToken): CommentThread[];
+ }
+
export interface ICodeLensSymbol {
range: IRange;
id?: string;
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index 5d0f2e25d86..d2429fec428 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -723,4 +723,26 @@ declare module 'vscode' {
}
//#endregion
+
+ interface CommentThread {
+ range: Range;
+ comments: Comment[];
+ }
+
+ interface Comment {
+ body: MarkdownString;
+ userName: string;
+ }
+
+ /**
+ * TODO: force update event?
+ * TODO: resolve step?
+ */
+ interface CommentProvider {
+ provideComments(document: TextDocument, token: CancellationToken): Promise;
+ }
+
+ namespace workspace {
+ export function registerCommentProvider(provider: CommentProvider): Disposable;
+ }
}
diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts
index 3418ddaf1a2..2949908a2f5 100644
--- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts
+++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts
@@ -48,6 +48,7 @@ import './mainThreadTerminalService';
import './mainThreadTreeViews';
import './mainThreadLogService';
import './mainThreadWebview';
+import './mainThreadComments';
import './mainThreadWindow';
import './mainThreadWorkspace';
diff --git a/src/vs/workbench/api/electron-browser/mainThreadComments.ts b/src/vs/workbench/api/electron-browser/mainThreadComments.ts
new file mode 100644
index 00000000000..ada23f39c96
--- /dev/null
+++ b/src/vs/workbench/api/electron-browser/mainThreadComments.ts
@@ -0,0 +1,77 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
+import { ITextModel } from 'vs/editor/common/model';
+import * as modes from 'vs/editor/common/modes';
+import { ReviewController } from 'vs/editor/contrib/review/review';
+import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
+import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
+import { keys } from '../../../base/common/map';
+import { IWorkbenchEditorService } from '../../services/editor/common/editorService';
+import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../node/extHost.protocol';
+
+@extHostNamedCustomer(MainContext.MainThreadComments)
+export class MainThreadComments extends Disposable implements MainThreadCommentsShape {
+
+ private _proxy: ExtHostCommentsShape;
+ private _providers = new Map();
+
+ constructor(
+ extHostContext: IExtHostContext,
+ @IEditorGroupService editorGroupService: IEditorGroupService,
+ @IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
+ @ICodeEditorService private _codeEditorService: ICodeEditorService
+ ) {
+ super();
+ this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments);
+ editorGroupService.onEditorsChanged(e => {
+ const outerEditor = this.getFocusedEditor();
+ if (!outerEditor) {
+ return;
+ }
+
+ const controller = ReviewController.get(outerEditor);
+ if (!controller) {
+ return;
+ }
+
+ this.provideComments(outerEditor.getModel()).then(commentThreads => {
+ controller.setComments(commentThreads);
+ });
+ });
+ }
+
+ $registerCommentProvider(handle: number): void {
+ this._providers.set(handle, undefined);
+ }
+
+ $unregisterCommentProvider(handle: number): void {
+ throw new Error('Method not implemented.');
+ }
+
+ dispose(): void {
+ throw new Error('Method not implemented.');
+ }
+
+ getFocusedEditor(): ICodeEditor {
+ let editor = this._codeEditorService.getFocusedCodeEditor();
+ // if\
+
+ return editor;
+ }
+
+ async provideComments(model: ITextModel): Promise {
+ const result: modes.CommentThread[] = [];
+ for (const handle of keys(this._providers)) {
+ result.push(...await this._proxy.$providerComments(handle, model.uri));
+ }
+ return result;
+ }
+
+}
diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts
index 82fc8af5fea..2b7349f2a93 100644
--- a/src/vs/workbench/api/node/extHost.api.impl.ts
+++ b/src/vs/workbench/api/node/extHost.api.impl.ts
@@ -58,6 +58,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { OverviewRulerLane } from 'vs/editor/common/model';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview';
+import { ExtHostComments } from './extHostComments';
export interface IExtensionApiFactory {
(extension: IExtensionDescription): typeof vscode;
@@ -119,6 +120,7 @@ export function createApiFactory(
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
+ const exthostCommentProviders = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostDocuments));
// Check that no named customers are missing
const expected: ProxyIdentifier[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]);
@@ -540,6 +542,9 @@ export function createApiFactory(
}),
registerSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
return extHostFileSystem.registerSearchProvider(scheme, provider);
+ }),
+ registerCommentProvider: proposedApiFunction(extension, (provider: vscode.CommentProvider) => {
+ return exthostCommentProviders.registerCommentProvider(provider);
})
};
diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts
index 8a1b10bc57b..d45d9f01b71 100644
--- a/src/vs/workbench/api/node/extHost.protocol.ts
+++ b/src/vs/workbench/api/node/extHost.protocol.ts
@@ -104,6 +104,11 @@ export interface MainThreadCommandsShape extends IDisposable {
$getCommands(): Thenable;
}
+export interface MainThreadCommentsShape extends IDisposable {
+ $registerCommentProvider(handle: number): void;
+ $unregisterCommentProvider(handle: number): void;
+}
+
export interface MainThreadConfigurationShape extends IDisposable {
$updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: UriComponents): TPromise;
$removeConfigurationOption(target: ConfigurationTarget, key: string, resource: UriComponents): TPromise;
@@ -813,10 +818,15 @@ export interface ExtHostProgressShape {
$acceptProgressCanceled(handle: number): void;
}
+export interface ExtHostCommentsShape {
+ $providerComments(handle: number, document: UriComponents): TPromise;
+}
+
// --- proxy identifiers
export const MainContext = {
MainThreadCommands: >createMainId('MainThreadCommands'),
+ MainThreadComments: createMainId('MainThreadComments'),
MainThreadConfiguration: createMainId('MainThreadConfiguration'),
MainThreadDebugService: createMainId('MainThreadDebugService'),
MainThreadDecorations: createMainId('MainThreadDecorations'),
@@ -871,5 +881,6 @@ export const ExtHostContext = {
ExtHostWorkspace: createExtId('ExtHostWorkspace'),
ExtHostWindow: createExtId('ExtHostWindow'),
ExtHostWebviews: createExtId('ExtHostWebviews'),
- ExtHostProgress: createMainId('ExtHostProgress')
+ ExtHostProgress: createMainId('ExtHostProgress'),
+ ExtHostComments: createMainId('ExtHostComments')
};
diff --git a/src/vs/workbench/api/node/extHostComments.ts b/src/vs/workbench/api/node/extHostComments.ts
new file mode 100644
index 00000000000..4ebd9ffd66b
--- /dev/null
+++ b/src/vs/workbench/api/node/extHostComments.ts
@@ -0,0 +1,76 @@
+/*---------------------------------------------------------------------------------------------
+* Copyright (c) Microsoft Corporation. All rights reserved.
+* Licensed under the MIT License. See License.txt in the project root for license information.
+*--------------------------------------------------------------------------------------------*/
+'use strict';
+
+import { asWinJsPromise } from 'vs/base/common/async';
+import { values } from 'vs/base/common/map';
+import URI, { UriComponents } from 'vs/base/common/uri';
+import { TPromise } from 'vs/base/common/winjs.base';
+import * as modes from 'vs/editor/common/modes';
+import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
+import * as vscode from 'vscode';
+import { flatten } from '../../../base/common/arrays';
+import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol';
+
+import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
+
+
+export class ExtHostComments implements ExtHostCommentsShape {
+ private static handlePool = 0;
+
+ private _proxy: MainThreadCommentsShape;
+
+ private _providers = new Map();
+
+ constructor(
+ mainContext: IMainContext,
+ private readonly _documents: ExtHostDocuments,
+ ) {
+ this._proxy = mainContext.getProxy(MainContext.MainThreadComments);
+ }
+
+ registerCommentProvider(
+ provider: vscode.CommentProvider
+ ): vscode.Disposable {
+ const handle = ExtHostComments.handlePool++;
+ this._providers.set(handle, provider);
+
+ this._proxy.$registerCommentProvider(handle);
+ return {
+ dispose: () => {
+ this._proxy.$unregisterCommentProvider(handle);
+ this._providers.delete(handle);
+ }
+ };
+ }
+
+ $providerComments(handle: number, uri: UriComponents): TPromise {
+ const data = this._documents.getDocumentData(URI.revive(uri));
+ if (!data || !data.document) {
+ return TPromise.as([]);
+ }
+
+ return asWinJsPromise(token => {
+ const allProviderResults = values(this._providers).map(provider => provider.provideComments(data.document, token));
+ return TPromise.join(allProviderResults);
+ })
+ .then(flatten)
+ .then(comments => comments.map(convertCommentThread));
+ }
+}
+
+function convertCommentThread(vscodeCommentThread: vscode.CommentThread): modes.CommentThread {
+ return {
+ range: extHostTypeConverter.fromRange(vscodeCommentThread.range),
+ comments: vscodeCommentThread.comments.map(convertComment)
+ };
+}
+
+function convertComment(vscodeComment: vscode.Comment): modes.Comment {
+ return {
+ body: extHostTypeConverter.MarkdownString.from(vscodeComment.body),
+ userName: vscodeComment.userName
+ };
+}