diff --git a/extensions/git-extended/src/common/models/repository.ts b/extensions/git-extended/src/common/models/repository.ts index 57a66d9bbe58ebd1b4e397a3ab1d900ea80290a3..cfc53cb1d38349c39e5dbf17045a8701e083ad8a 100644 --- a/extensions/git-extended/src/common/models/repository.ts +++ b/extensions/git-extended/src/common/models/repository.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { Remote } from './remote'; import { GitProcess } from 'dugite'; -import { uniqBy } from '../util'; +import { uniqBy, anyEvent, filterEvent, isDescendant } from '../util'; import { parseRemote } from '../remote'; import { CredentialStore } from '../../credentials'; import { PullRequest, PRType } from './pullrequest'; @@ -58,11 +58,31 @@ export class Repository { return this._remotes; } + private statusTimeout: any; + private disposables: vscode.Disposable[] = []; + constructor(path: string, workspaceState: vscode.Memento) { this.path = path; + + const fsWatcher = vscode.workspace.createFileSystemWatcher('**'); + this.disposables.push(fsWatcher); + + const onWorkspaceChange = anyEvent(fsWatcher.onDidChange, fsWatcher.onDidCreate, fsWatcher.onDidDelete); + const onRepositoryChange = filterEvent(onWorkspaceChange, uri => isDescendant(this.path, uri.fsPath)); + const onRelevantRepositoryChange = filterEvent(onRepositoryChange, uri => !/\/\.git(\/index\.lock)?$/.test(uri.path)); + onRelevantRepositoryChange(this.onFSChange, this, this.disposables); + this.status(); } + onFSChange() { + clearTimeout(this.statusTimeout); + + this.statusTimeout = setTimeout(() => { + this.status(); + }, 1000); + } + async status() { let HEAD: Branch | undefined; diff --git a/extensions/git-extended/src/common/util.ts b/extensions/git-extended/src/common/util.ts index 5c04757b62a7df0519065a0a268a4bb902412bae..5ca37b8e1688bbe450105a45f541376e2b8c46b1 100644 --- a/extensions/git-extended/src/common/util.ts +++ b/extensions/git-extended/src/common/util.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import { Event } from 'vscode'; +import { sep } from 'path'; export function uniqBy(arr: T[], fn: (el: T) => string): T[] { const seen = Object.create(null); @@ -18,4 +20,59 @@ export function uniqBy(arr: T[], fn: (el: T) => string): T[] { seen[key] = true; return true; }); -} \ No newline at end of file +} + +export interface IDisposable { + dispose(): void; +} + +export function dispose(disposables: T[]): T[] { + disposables.forEach(d => d.dispose()); + return []; +} + +export function toDisposable(dispose: () => void): IDisposable { + return { dispose }; +} + +export function combinedDisposable(disposables: IDisposable[]): IDisposable { + return toDisposable(() => dispose(disposables)); +} + +export function anyEvent(...events: Event[]): Event { + return (listener, thisArgs = null, disposables?) => { + const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i)))); + + if (disposables) { + disposables.push(result); + } + + return result; + }; +} + +export function filterEvent(event: Event, filter: (e: T) => boolean): Event { + return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); +} + +function isWindowsPath(path: string): boolean { + return /^[a-zA-Z]:\\/.test(path); +} + +export function isDescendant(parent: string, descendant: string): boolean { + if (parent === descendant) { + return true; + } + + if (parent.charAt(parent.length - 1) !== sep) { + parent += sep; + } + + // Windows is case insensitive + if (isWindowsPath(parent)) { + parent = parent.toLowerCase(); + descendant = descendant.toLowerCase(); + } + + return descendant.startsWith(parent); +} diff --git a/extensions/git-extended/src/review/reviewMode.ts b/extensions/git-extended/src/review/reviewMode.ts index 4343fd6887022cb7128243d061dee30c5377ce7f..32d7b858e5b4745a82ec97e38553da3e2a637a14 100644 --- a/extensions/git-extended/src/review/reviewMode.ts +++ b/extensions/git-extended/src/review/reviewMode.ts @@ -23,8 +23,6 @@ export interface ReviewState { branch: string; head: any; base: any; - fileChanges: any; - comments: any; } export class ReviewMode { @@ -60,7 +58,6 @@ export class ReviewMode { })); this.validateState(); - this.pollForStatusChange(); } @@ -93,7 +90,12 @@ export class ReviewMode { } this._prNumber = state.prNumber; - this._lastCommitSha = state['head'].sha; + if (!state.head || !state.base) { + // load pr + this._lastCommitSha = null; + } else { + this._lastCommitSha = state['head'].sha; + } // we switch to another PR, let's clean up first. this.clear(); @@ -104,6 +106,13 @@ export class ReviewMode { } const pr = await githubRepo.getPullRequest(this._prNumber); + state.base = pr.prItem.base; + state.head = pr.prItem.head; + this._workspaceState.update(`${REVIEW_STATE}:${branch}`, state); + if (!this._lastCommitSha) { + this._lastCommitSha = state.head.sha; + } + await this.getPullRequestData(pr); let prChangeResources = this._localFileChanges.map(fileChange => ({ @@ -288,7 +297,7 @@ export class ReviewMode { } registerCommentProvider() { - vscode.workspace.registerCommentProvider({ + this._commentProvider = vscode.workspace.registerCommentProvider({ onDidChangeCommentThreads: this._onDidChangeCommentThreads.event, provideComments: async (document: vscode.TextDocument, token: vscode.CancellationToken) => { let lastLine = document.lineCount; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 63032a877145d8c8e1acc275055b80ad96101e77..fa0a8afc25f02e99fe78d33374d6d739bf29dddf 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -784,11 +784,6 @@ declare module 'vscode' { reply?: Command; } - interface NewCommentAction { - ranges: Range[]; - actions?: Command[]; - } - interface Comment { commentId: string; body: MarkdownString;