Skip to content

Commit e8168ef

Browse files
authored
Merge pull request #134 from edgardmessias/status_file_explorer
Added support to SVN status in file explorer (Close #34)
2 parents 6adad70 + 30e834b commit e8168ef

16 files changed

+647
-33
lines changed

.bithoundrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"ignore": [
33
"**/node_modules/**",
44
"**/out/**",
5+
"**/src/types/*.d.ts",
56
"**/src/*.d.ts"
67
],
78
"test": [

.travis.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ os:
99
- linux
1010
- osx
1111

12+
matrix:
13+
include:
14+
- env: CODE_VERSION=1.17.2
15+
- env: CODE_VERSION=1.18.1
16+
- env: CODE_VERSION=1.19.3
17+
- env: CODE_VERSION=insiders
18+
allow_failures:
19+
- env: CODE_VERSION=insiders
20+
1221
cache:
1322
directories:
1423
- "node_modules"
@@ -29,4 +38,6 @@ script:
2938
- npm test --silent
3039

3140
after_success:
32-
- bash <(curl -s https://codecov.io/bash)
41+
- if [ "$TRAVIS_OS_NAME" == "linux" && "$CODE_VERSION" == "" ]; then
42+
bash <(curl -s https://codecov.io/bash)
43+
fi

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,39 @@ If you use [TortoiseSVN](https://tortoisesvn.net/), make sure the option
6464
* Commit changes/changelists
6565
* See commit messages
6666

67+
## Experimental
68+
69+
### * SVN Status in File Explorer (See #34)
70+
How to enable:
71+
* Open the file: `<vscode path>\resources\app\product.json`
72+
* Find `extensionAllowedProposedApi`
73+
* Append `"johnstoncode.svn-scm"` in the array
74+
75+
Example:
76+
```json
77+
// FROM
78+
{
79+
"extensionAllowedProposedApi": [
80+
"ms-vsliveshare.vsliveshare"
81+
]
82+
}
83+
// TO
84+
{
85+
"extensionAllowedProposedApi": [
86+
"ms-vsliveshare.vsliveshare", "johnstoncode.svn-scm"
87+
]
88+
}
89+
```
90+
6791
## Settings
6892

6993
`svn.enabled`
7094
* Enables Svn as a SCM in VS Code.
7195
`"default"` &mdash; `true`
7296

73-
`svn.path`
74-
* Path to the svn executable
75-
`"default"` &mdash; `null`
97+
`svn.decorations.enabled`
98+
* Controls if SVN contributes colors and badges to the explorer and the open (VSCode >= 1.18 with proposed-api enabled)
99+
`"default"` &mdash; `true`
76100

77101
`svn.diff.withHead`
78102
* Show diff changes using latest revision in the repository. Set false to use latest revision in local folder

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,12 @@
316316
"description": "Whether svn is enabled",
317317
"default": true
318318
},
319+
"svn.decorations.enabled": {
320+
"type": "boolean",
321+
"description":
322+
"Controls if SVN contributes colors and badges to the explorer and the open (VSCode >= 1.18 with proposed-api)",
323+
"default": true
324+
},
319325
"svn.path": {
320326
"type": ["string", "null"],
321327
"description": "Path to the svn executable",

src/decorationProvider.ts

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
"use strict";
7+
8+
import {
9+
window,
10+
workspace,
11+
Uri,
12+
Disposable,
13+
Event,
14+
EventEmitter,
15+
DecorationData,
16+
DecorationProvider,
17+
ThemeColor
18+
} from "vscode";
19+
import { Repository } from "./repository";
20+
import { Model } from "./model";
21+
import { debounce } from "./decorators";
22+
import { filterEvent, isDescendant } from "./util";
23+
import { SvnResourceGroup } from "./resource";
24+
import { Status } from "./svn";
25+
import * as path from "path";
26+
27+
class SvnIgnoreDecorationProvider implements DecorationProvider {
28+
private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
29+
readonly onDidChangeDecorations: Event<Uri[]> = this._onDidChangeDecorations
30+
.event;
31+
32+
private checkIgnoreQueue = new Map<
33+
string,
34+
{ resolve: (status: boolean) => void; reject: (err: any) => void }
35+
>();
36+
private disposables: Disposable[] = [];
37+
38+
constructor(private repository: Repository) {
39+
this.disposables.push(
40+
window.registerDecorationProvider(this),
41+
repository.onDidChangeStatus(_ => this._onDidChangeDecorations.fire())
42+
);
43+
}
44+
45+
dispose(): void {
46+
this.disposables.forEach(d => d.dispose());
47+
this.checkIgnoreQueue.clear();
48+
}
49+
50+
provideDecoration(uri: Uri): Promise<DecorationData | undefined> {
51+
return new Promise<boolean>((resolve, reject) => {
52+
this.checkIgnoreQueue.set(uri.fsPath, { resolve, reject });
53+
this.checkIgnoreSoon();
54+
}).then(ignored => {
55+
if (ignored) {
56+
return <DecorationData>{
57+
priority: 3,
58+
color: new ThemeColor("gitDecoration.ignoredResourceForeground")
59+
};
60+
}
61+
});
62+
}
63+
64+
@debounce(500)
65+
private checkIgnoreSoon(): void {
66+
const queue = new Map(this.checkIgnoreQueue.entries());
67+
this.checkIgnoreQueue.clear();
68+
69+
const ignored = this.repository.statusIgnored;
70+
const external = this.repository.statusExternal;
71+
72+
const files = ignored.map(stat =>
73+
path.join(this.repository.workspaceRoot, stat.path)
74+
);
75+
76+
files.push(
77+
...external.map(stat =>
78+
path.join(this.repository.workspaceRoot, stat.path)
79+
)
80+
);
81+
82+
for (const [key, value] of queue.entries()) {
83+
value.resolve(files.some(file => isDescendant(file, key)));
84+
}
85+
}
86+
}
87+
88+
class SvnDecorationProvider implements DecorationProvider {
89+
private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
90+
readonly onDidChangeDecorations: Event<Uri[]> = this._onDidChangeDecorations
91+
.event;
92+
93+
private disposables: Disposable[] = [];
94+
private decorations = new Map<string, DecorationData>();
95+
96+
constructor(private repository: Repository) {
97+
this.disposables.push(
98+
window.registerDecorationProvider(this),
99+
// repository.onDidRunOperation(this.onDidRunOperation, this)
100+
repository.onDidChangeStatus(this.onDidRunOperation, this)
101+
);
102+
}
103+
104+
private onDidRunOperation(): void {
105+
let newDecorations = new Map<string, DecorationData>();
106+
this.collectDecorationData(this.repository.changes, newDecorations);
107+
this.collectDecorationData(this.repository.unversioned, newDecorations);
108+
this.collectDecorationData(this.repository.conflicts, newDecorations);
109+
110+
this.repository.changelists.forEach((group, changelist) => {
111+
this.collectDecorationData(group, newDecorations);
112+
});
113+
114+
let uris: Uri[] = [];
115+
newDecorations.forEach((value, uriString) => {
116+
if (this.decorations.has(uriString)) {
117+
this.decorations.delete(uriString);
118+
} else {
119+
uris.push(Uri.parse(uriString));
120+
}
121+
});
122+
this.decorations.forEach((value, uriString) => {
123+
uris.push(Uri.parse(uriString));
124+
});
125+
this.decorations = newDecorations;
126+
this._onDidChangeDecorations.fire(uris);
127+
}
128+
129+
private collectDecorationData(
130+
group: SvnResourceGroup,
131+
bucket: Map<string, DecorationData>
132+
): void {
133+
group.resourceStates.forEach(r => {
134+
if (r.resourceDecoration) {
135+
bucket.set(r.resourceUri.toString(), r.resourceDecoration);
136+
}
137+
});
138+
}
139+
140+
provideDecoration(uri: Uri): DecorationData | undefined {
141+
return this.decorations.get(uri.toString());
142+
}
143+
144+
dispose(): void {
145+
this.disposables.forEach(d => d.dispose());
146+
}
147+
}
148+
149+
export class SvnDecorations {
150+
private configListener: Disposable;
151+
private modelListener: Disposable[] = [];
152+
private providers = new Map<Repository, Disposable>();
153+
154+
constructor(private model: Model) {
155+
// this.configListener = workspace.onDidChangeConfiguration(
156+
// e => e.affectsConfiguration("svn.decorations.enabled") && this.update()
157+
// );
158+
this.update();
159+
}
160+
161+
private update(): void {
162+
const enabled = workspace
163+
.getConfiguration()
164+
.get<boolean>("svn.decorations.enabled");
165+
if (enabled) {
166+
this.enable();
167+
} else {
168+
this.disable();
169+
}
170+
}
171+
172+
private enable(): void {
173+
this.modelListener = [];
174+
this.model.onDidOpenRepository(
175+
this.onDidOpenRepository,
176+
this,
177+
this.modelListener
178+
);
179+
// this.model.onDidCloseRepository(
180+
// this.onDidCloseRepository,
181+
// this,
182+
// this.modelListener
183+
// );
184+
this.model.repositories.forEach(this.onDidOpenRepository, this);
185+
}
186+
187+
private disable(): void {
188+
this.modelListener.forEach(d => d.dispose());
189+
this.providers.forEach(value => value.dispose());
190+
this.providers.clear();
191+
}
192+
193+
private onDidOpenRepository(repository: Repository): void {
194+
const provider = new SvnDecorationProvider(repository);
195+
const ignoreProvider = new SvnIgnoreDecorationProvider(repository);
196+
this.providers.set(repository, Disposable.from(provider, ignoreProvider));
197+
}
198+
199+
private onDidCloseRepository(repository: Repository): void {
200+
const provider = this.providers.get(repository);
201+
if (provider) {
202+
provider.dispose();
203+
this.providers.delete(repository);
204+
}
205+
}
206+
207+
dispose(): void {
208+
this.configListener.dispose();
209+
this.modelListener.forEach(d => d.dispose());
210+
this.providers.forEach(value => value.dispose);
211+
this.providers.clear();
212+
}
213+
}

src/extension.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { SvnFinder } from "./svnFinder";
1010
import { SvnContentProvider } from "./svnContentProvider";
1111
import { SvnCommands } from "./commands";
1212
import { Model } from "./model";
13-
import { toDisposable } from "./util";
13+
import { toDisposable, hasSupportToDecorationProvider } from "./util";
1414

1515
async function init(context: ExtensionContext, disposables: Disposable[]) {
1616
const outputChannel = window.createOutputChannel("Svn");
@@ -35,6 +35,13 @@ async function init(context: ExtensionContext, disposables: Disposable[]) {
3535
const svnCommands = new SvnCommands(model);
3636
disposables.push(model, contentProvider);
3737

38+
// First, check the vscode has support to DecorationProvider
39+
if (hasSupportToDecorationProvider()) {
40+
import("./decorationProvider").then(provider => {
41+
const decoration = new provider.SvnDecorations(model);
42+
disposables.push(decoration);
43+
});
44+
}
3845
const onRepository = () =>
3946
commands.executeCommand(
4047
"setContext",

src/messages.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ export function inputCommitMessage(message?: string) {
1818
prompt: "Please enter a commit message",
1919
ignoreFocusOut: true
2020
})
21-
.then(string => resolve(string));
21+
.then(input => resolve(input));
2222
});
2323
}

0 commit comments

Comments
 (0)