Skip to content

Commit 4c82433

Browse files
authored
Merge pull request #109 from edgardmessias/improved_external_changes
Improved external changes - Good stuff
2 parents f0b3619 + 36cece8 commit 4c82433

File tree

7 files changed

+228
-23
lines changed

7 files changed

+228
-23
lines changed

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]) {
2727
const model = new Model(svn);
2828
const contentProvider = new SvnContentProvider(model);
2929
const commands = new SvnCommands(model);
30-
disposables.push(model);
30+
disposables.push(model, contentProvider);
3131

3232
outputChannel.appendLine("Using svn " + info.version + " from " + info.path);
3333

src/model.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
Uri,
44
window,
55
Disposable,
6-
WorkspaceFoldersChangeEvent
6+
WorkspaceFoldersChangeEvent,
7+
EventEmitter,
8+
Event
79
} from "vscode";
810
import * as fs from "fs";
911
import * as path from "path";
@@ -13,11 +15,25 @@ import { Svn } from "./svn";
1315
import { dispose, anyEvent, filterEvent } from "./util";
1416
import { sequentialize } from "./decorators";
1517

18+
export interface ModelChangeEvent {
19+
repository: Repository;
20+
uri: Uri;
21+
}
22+
23+
export interface OriginalResourceChangeEvent {
24+
repository: Repository;
25+
uri: Uri;
26+
}
27+
1628
interface OpenRepository {
1729
repository: Repository;
1830
}
1931

2032
export class Model {
33+
private _onDidChangeRepository = new EventEmitter<ModelChangeEvent>();
34+
readonly onDidChangeRepository: Event<ModelChangeEvent> = this
35+
._onDidChangeRepository.event;
36+
2137
public openRepositories: OpenRepository[] = [];
2238
private disposables: Disposable[] = [];
2339
private enabled = false;
@@ -199,7 +215,10 @@ export class Model {
199215
fs.readdirSync(path).forEach(file => {
200216
const dir = path + "/" + file;
201217

202-
if (fs.statSync(dir).isDirectory() && !micromatch.some([dir], this.ignoreList)) {
218+
if (
219+
fs.statSync(dir).isDirectory() &&
220+
!micromatch.some([dir], this.ignoreList)
221+
) {
203222
this.tryOpenRepository(dir, newLevel);
204223
}
205224
});
@@ -257,6 +276,12 @@ export class Model {
257276
}
258277

259278
private open(repository: Repository): void {
279+
const changeListener = repository.onDidChangeRepository(uri =>
280+
this._onDidChangeRepository.fire({ repository, uri })
281+
);
282+
283+
this.disposables.push(changeListener);
284+
260285
this.openRepositories.push({ repository });
261286
}
262287

src/repository.ts

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { dispose, anyEvent, filterEvent, toDisposable } from "./util";
1919
import * as path from "path";
2020
import * as micromatch from "micromatch";
2121
import { setInterval, clearInterval } from "timers";
22+
import { toSvnUri } from "./uri";
2223

2324
export class Repository {
2425
public watcher: FileSystemWatcher;
@@ -38,6 +39,9 @@ export class Repository {
3839
private _onDidChangeStatus = new EventEmitter<void>();
3940
readonly onDidChangeStatus: Event<void> = this._onDidChangeStatus.event;
4041

42+
private _onDidChangeBranch = new EventEmitter<void>();
43+
readonly onDidChangeBranch: Event<void> = this._onDidChangeBranch.event;
44+
4145
get root(): string {
4246
return this.repository.root;
4347
}
@@ -59,25 +63,37 @@ export class Repository {
5963
fsWatcher.onDidCreate,
6064
fsWatcher.onDidDelete
6165
);
66+
6267
const onRepositoryChange = filterEvent(
6368
onWorkspaceChange,
6469
uri => !/^\.\./.test(path.relative(repository.root, uri.fsPath))
6570
);
6671
const onRelevantRepositoryChange = filterEvent(
6772
onRepositoryChange,
68-
uri => !/\/\.svn\/tmp/.test(uri.path)
73+
uri => !/[\\\/]\.svn[\\\/]tmp/.test(uri.path)
6974
);
7075
onRelevantRepositoryChange(this.update, this, this.disposables);
7176

7277
const onRelevantSvnChange = filterEvent(onRelevantRepositoryChange, uri =>
73-
/\/\.svn\//.test(uri.path)
78+
/[\\\/]\.svn[\\\/]/.test(uri.path)
7479
);
80+
7581
onRelevantSvnChange(
7682
this._onDidChangeRepository.fire,
7783
this._onDidChangeRepository,
7884
this.disposables
7985
);
8086

87+
this.onDidChangeRepository(
88+
async () => {
89+
if (!this.isSwitchingBranch) {
90+
this.currentBranch = await this.getCurrentBranch();
91+
}
92+
},
93+
null,
94+
this.disposables
95+
);
96+
8197
this.sourceControl = scm.createSourceControl(
8298
"svn",
8399
"SVN",
@@ -98,12 +114,12 @@ export class Repository {
98114
null,
99115
this.disposables
100116
);
101-
this.onDidChangeRepository(
102-
() => (this.sourceControl.statusBarCommands = statusBar.commands),
103-
null,
104-
this.disposables
105-
);
106-
this.sourceControl.statusBarCommands = statusBar.commands;
117+
118+
const updateBranchName = async () => {
119+
this.currentBranch = await this.getCurrentBranch();
120+
this.sourceControl.statusBarCommands = statusBar.commands;
121+
};
122+
updateBranchName();
107123

108124
this.changes = this.sourceControl.createResourceGroup("changes", "Changes");
109125
this.notTracked = this.sourceControl.createResourceGroup(
@@ -196,8 +212,6 @@ export class Repository {
196212
this.changes.resourceStates = changes;
197213
this.notTracked.resourceStates = notTracked;
198214

199-
this.currentBranch = await this.getCurrentBranch();
200-
201215
this._onDidChangeStatus.fire();
202216

203217
return Promise.resolve();
@@ -208,7 +222,7 @@ export class Repository {
208222
return;
209223
}
210224

211-
return uri.with({ scheme: "svn", query: uri.path, path: uri.path });
225+
return toSvnUri(uri, "");
212226
}
213227

214228
show(filePath: string, revision?: string): Promise<string> {
@@ -229,17 +243,17 @@ export class Repository {
229243

230244
async branch(name: string) {
231245
this.isSwitchingBranch = true;
232-
this._onDidChangeRepository.fire();
246+
this._onDidChangeBranch.fire();
233247
const response = await this.repository.branch(name);
234248
this.isSwitchingBranch = false;
235249
this.updateBranches();
236-
this._onDidChangeRepository.fire();
250+
this._onDidChangeBranch.fire();
237251
return response;
238252
}
239253

240254
async switchBranch(name: string) {
241255
this.isSwitchingBranch = true;
242-
this._onDidChangeRepository.fire();
256+
this._onDidChangeBranch.fire();
243257

244258
try {
245259
const response = await this.repository.switchBranch(name);
@@ -257,7 +271,7 @@ export class Repository {
257271
} finally {
258272
this.isSwitchingBranch = false;
259273
this.updateBranches();
260-
this._onDidChangeRepository.fire();
274+
this._onDidChangeBranch.fire();
261275
}
262276
}
263277
}

src/statusBar.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ export class SvnStatusBar {
1010
}
1111

1212
constructor(private repository: Repository) {
13-
repository.onDidChangeStatus(
13+
repository.onDidChangeBranch(
1414
this._onDidChange.fire,
1515
this._onDidChange,
1616
this.disposables
1717
);
18+
repository.onDidChangeRepository(
19+
() => {
20+
if (!this.repository.isSwitchingBranch) {
21+
this._onDidChange.fire();
22+
}
23+
},
24+
null,
25+
this.disposables
26+
);
1827
}
1928

2029
get commands() {

src/svnContentProvider.ts

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,87 @@
1-
import { workspace, Uri } from "vscode";
2-
import { Model } from "./model";
1+
import {
2+
workspace,
3+
Uri,
4+
TextDocumentContentProvider,
5+
EventEmitter,
6+
Event,
7+
Disposable,
8+
window
9+
} from "vscode";
10+
import { Model, ModelChangeEvent, OriginalResourceChangeEvent } from "./model";
11+
import { toSvnUri, fromSvnUri } from "./uri";
12+
import { throttle, debounce } from "./decorators";
13+
import {
14+
filterEvent,
15+
eventToPromise,
16+
isDescendant,
17+
toDisposable
18+
} from "./util";
19+
20+
interface CacheRow {
21+
uri: Uri;
22+
timestamp: number;
23+
}
24+
25+
interface Cache {
26+
[uri: string]: CacheRow;
27+
}
28+
29+
const THREE_MINUTES = 1000 * 60 * 3;
30+
const FIVE_MINUTES = 1000 * 60 * 5;
31+
32+
export class SvnContentProvider implements TextDocumentContentProvider {
33+
private _onDidChange = new EventEmitter<Uri>();
34+
get onDidChange(): Event<Uri> {
35+
return this._onDidChange.event;
36+
}
37+
38+
private changedRepositoryRoots = new Set<string>();
39+
private cache: Cache = Object.create(null);
40+
private disposables: Disposable[] = [];
341

4-
export class SvnContentProvider {
542
constructor(private model: Model) {
6-
workspace.registerTextDocumentContentProvider("svn", this);
43+
this.disposables.push(
44+
model.onDidChangeRepository(this.onDidChangeRepository, this),
45+
workspace.registerTextDocumentContentProvider("svn", this)
46+
);
47+
48+
const interval = setInterval(() => this.cleanup(), FIVE_MINUTES);
49+
this.disposables.push(toDisposable(() => clearInterval(interval)));
50+
}
51+
52+
private onDidChangeRepository({ repository }: ModelChangeEvent): void {
53+
this.changedRepositoryRoots.add(repository.root);
54+
this.eventuallyFireChangeEvents();
55+
}
56+
57+
@debounce(1100)
58+
private eventuallyFireChangeEvents(): void {
59+
this.fireChangeEvents();
60+
}
61+
62+
@throttle
63+
private async fireChangeEvents(): Promise<void> {
64+
if (!window.state.focused) {
65+
const onDidFocusWindow = filterEvent(
66+
window.onDidChangeWindowState,
67+
e => e.focused
68+
);
69+
await eventToPromise(onDidFocusWindow);
70+
}
71+
72+
Object.keys(this.cache).forEach(key => {
73+
const uri = this.cache[key].uri;
74+
const fsPath = uri.fsPath;
75+
76+
for (const root of this.changedRepositoryRoots) {
77+
if (isDescendant(root, fsPath)) {
78+
this._onDidChange.fire(uri);
79+
return;
80+
}
81+
}
82+
});
83+
84+
this.changedRepositoryRoots.clear();
785
}
886

987
async provideTextDocumentContent(uri: Uri): Promise<string> {
@@ -13,6 +91,12 @@ export class SvnContentProvider {
1391
return "";
1492
}
1593

94+
const cacheKey = uri.toString();
95+
const timestamp = new Date().getTime();
96+
const cacheValue: CacheRow = { uri, timestamp };
97+
98+
this.cache[cacheKey] = cacheValue;
99+
16100
let revision = undefined;
17101

18102
const config = workspace.getConfiguration("svn");
@@ -28,4 +112,27 @@ export class SvnContentProvider {
28112
return "";
29113
}
30114
}
115+
116+
private cleanup(): void {
117+
const now = new Date().getTime();
118+
const cache = Object.create(null);
119+
120+
Object.keys(this.cache).forEach(key => {
121+
const row = this.cache[key];
122+
const { path } = fromSvnUri(row.uri);
123+
const isOpen = workspace.textDocuments
124+
.filter(d => d.uri.scheme === "file")
125+
.some(d => d.uri.fsPath === path);
126+
127+
if (isOpen || now - row.timestamp < THREE_MINUTES) {
128+
cache[row.uri.toString()] = row;
129+
}
130+
});
131+
132+
this.cache = cache;
133+
}
134+
135+
dispose(): void {
136+
this.disposables.forEach(d => d.dispose());
137+
}
31138
}

src/uri.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
import { Uri } from "vscode";
22

3+
export interface SvnUriParams {
4+
path: string;
5+
ref: string;
6+
}
7+
8+
export function fromSvnUri(uri: Uri): SvnUriParams {
9+
return JSON.parse(uri.query);
10+
}
11+
312
export function toSvnUri(uri: Uri, ref: string): Uri {
413
return uri.with({
514
scheme: "svn",

0 commit comments

Comments
 (0)