Skip to content

Commit 409f189

Browse files
authored
Merge pull request #2845 from akshita31/vscode_clipboard
Help users file issues using the clipboard
2 parents 4de1b39 + 8dfdd7f commit 409f189

File tree

8 files changed

+533
-148
lines changed

8 files changed

+533
-148
lines changed

package-lock.json

Lines changed: 417 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@
801801
},
802802
{
803803
"command": "csharp.reportIssue",
804-
"title": "Start authoring a new issue on GitHub",
804+
"title": "Report an issue",
805805
"category": "CSharp"
806806
},
807807
{

src/features/reportIssue.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default async function reportIssue(vscode: vscode, eventStream: EventStre
2020
let extensions = getInstalledExtensions(vscode);
2121
let csharpExtVersion = getCsharpExtensionVersion(vscode);
2222

23-
const body = encodeURIComponent(`## Issue Description ##
23+
const body =`## Issue Description ##
2424
## Steps to Reproduce ##
2525
2626
## Expected Behavior ##
@@ -46,11 +46,12 @@ ${dotnetInfo}</details>
4646
<details><summary>Visual Studio Code Extensions</summary>
4747
${generateExtensionTable(extensions)}
4848
</details>
49-
`);
50-
51-
const encodedBody = encodeURIComponent(body);
49+
`;
50+
5251
const queryStringPrefix: string = "?";
53-
const fullUrl = `${issuesUrl}${queryStringPrefix}body=${encodedBody}`;
52+
const issueDefault = "Please paste the output from your clipboard";
53+
const fullUrl = `${issuesUrl}${queryStringPrefix}body=${issueDefault}`;
54+
await vscode.env.clipboard.writeText(body);
5455
eventStream.post(new OpenURL(fullUrl));
5556
}
5657

src/observers/OpenURLObserver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export class OpenURLObserver {
1616
switch (event.type) {
1717
case EventType.OpenURL:
1818
let url = (<OpenURL>event).url;
19-
this.vscode.commands.executeCommand("vscode.open", this.vscode.Uri.parse(url));
19+
this.vscode.env.openExternal(this.vscode.Uri.parse(url));
2020
break;
2121
}
2222
}

src/vscodeAdapter.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,21 @@ export interface Uri {
418418
toJSON(): any;
419419
}
420420

421+
export interface Clipboard {
422+
423+
/**
424+
* Read the current clipboard contents as text.
425+
* @returns A thenable that resolves to a string.
426+
*/
427+
readText(): Thenable<string>;
428+
429+
/**
430+
* Writes text into the clipboard.
431+
* @returns A thenable that resolves when writing happened.
432+
*/
433+
writeText(value: string): Thenable<void>;
434+
}
435+
421436
export interface MessageItem {
422437

423438
/**
@@ -902,7 +917,7 @@ interface Thenable<T> {
902917
then<TResult>(onfulfilled?: (value: T) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
903918
}
904919

905-
export interface Extension<T>{
920+
export interface Extension<T> {
906921
readonly id: string;
907922
readonly packageJSON: any;
908923
}
@@ -935,4 +950,14 @@ export interface vscode {
935950
};
936951

937952
version: string;
953+
954+
env: {
955+
appName: string;
956+
appRoot: string;
957+
language: string;
958+
clipboard: Clipboard;
959+
machineId: string;
960+
sessionId: string;
961+
openExternal(target: Uri): Thenable<boolean>;
962+
};
938963
}

test/unitTests/features/reportIssue.test.ts

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ suite(`${reportIssue.name}`, () => {
4444
let eventBus: TestEventBus;
4545
let getDotnetInfo = FakeGetDotnetInfo;
4646
let options: Options;
47+
let issueBody: string;
4748

4849
setup(() => {
4950
vscode = getFakeVsCode();
@@ -55,6 +56,12 @@ suite(`${reportIssue.name}`, () => {
5556
id: ""
5657
};
5758
};
59+
60+
vscode.env.clipboard.writeText = (body:string) => {
61+
issueBody = body;
62+
return undefined;
63+
};
64+
5865
vscode.version = vscodeVersion;
5966
vscode.extensions.all = [extension1, extension2];
6067
eventStream = new EventStream();
@@ -72,60 +79,56 @@ suite(`${reportIssue.name}`, () => {
7279
test(`${OpenURL.name} event is created with the omnisharp-vscode github repo issues url`, async () => {
7380
await reportIssue(vscode, eventStream, getDotnetInfo, false, options, fakeMonoResolver);
7481
let url = (<OpenURL>eventBus.getEvents()[0]).url;
75-
expect(url).to.include("https://github.com/OmniSharp/omnisharp-vscode/issues/new");
82+
expect(url).to.include("https://github.com/OmniSharp/omnisharp-vscode/issues/new?body=Please paste the output from your clipboard");
7683
});
7784

78-
test("The url contains the vscode version", async () => {
79-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
80-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
81-
expect(url).to.include(encodeURIComponent(encodeURIComponent(`**VSCode version**: ${vscodeVersion}`)));
82-
});
85+
suite("The body is passed to the vscode clipboard and", () => {
86+
test("it contains the vscode version", async () => {
87+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
88+
expect(issueBody).to.include(`**VSCode version**: ${vscodeVersion}`);
89+
});
8390

84-
test("The body contains the csharp extension version", async () => {
85-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
86-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
87-
expect(url).to.include(encodeURIComponent(encodeURIComponent(`**C# Extension**: ${csharpExtVersion}`)));
88-
});
91+
test("it contains the csharp extension version", async () => {
92+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
93+
expect(issueBody).to.include(`**C# Extension**: ${csharpExtVersion}`);
94+
});
8995

90-
test("dotnet info is obtained and put into the url", async () => {
91-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
92-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
93-
expect(url).to.contain(fakeDotnetInfo);
94-
});
96+
test("it contains dotnet info", async () => {
97+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
98+
expect(issueBody).to.contain(fakeDotnetInfo);
99+
});
95100

96-
test("mono information is obtained when it is a valid mono platform", async () => {
97-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
98-
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
99-
});
101+
test("mono information is obtained when it is a valid mono platform", async () => {
102+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
103+
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
104+
});
100105

101-
test("mono version is put in the body when shouldUseGlobalMono returns a monoInfo", async () => {
102-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
103-
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
104-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
105-
expect(url).to.contain(fakeMonoInfo.version);
106-
});
106+
test("mono version is put in the body when shouldUseGlobalMono returns a monoInfo", async () => {
107+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
108+
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
109+
expect(issueBody).to.contain(fakeMonoInfo.version);
110+
});
107111

108-
test("built-in mono usage message is put in the body when shouldUseGlobalMono returns a null", async () => {
109-
fakeMonoResolver = new FakeMonoResolver(false);
110-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
111-
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
112-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
113-
expect(url).to.contain(encodeURIComponent(encodeURIComponent(`OmniSharp using built-in mono`)));
114-
});
112+
test("built-in mono usage message is put in the body when shouldUseGlobalMono returns a null", async () => {
113+
fakeMonoResolver = new FakeMonoResolver(false);
114+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
115+
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(true);
116+
expect(issueBody).to.contain(`OmniSharp using built-in mono`);
117+
});
115118

116-
test("mono information is not obtained when it is not a valid mono platform", async () => {
117-
await reportIssue(vscode, eventStream, getDotnetInfo, false, options, fakeMonoResolver);
118-
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(false);
119-
});
119+
test("mono information is not obtained when it is not a valid mono platform", async () => {
120+
await reportIssue(vscode, eventStream, getDotnetInfo, false, options, fakeMonoResolver);
121+
expect(fakeMonoResolver.getGlobalMonoCalled).to.be.equal(false);
122+
});
120123

121-
test("The url contains the name, publisher and version for all the extensions that are not builtin", async () => {
122-
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
123-
let url = (<OpenURL>eventBus.getEvents()[0]).url;
124-
expect(url).to.contain(extension2.packageJSON.name);
125-
expect(url).to.contain(extension2.packageJSON.publisher);
126-
expect(url).to.contain(extension2.packageJSON.version);
127-
expect(url).to.not.contain(extension1.packageJSON.name);
128-
expect(url).to.not.contain(extension1.packageJSON.publisher);
129-
expect(url).to.not.contain(extension1.packageJSON.version);
124+
test("The url contains the name, publisher and version for all the extensions that are not builtin", async () => {
125+
await reportIssue(vscode, eventStream, getDotnetInfo, isValidForMono, options, fakeMonoResolver);
126+
expect(issueBody).to.contain(extension2.packageJSON.name);
127+
expect(issueBody).to.contain(extension2.packageJSON.publisher);
128+
expect(issueBody).to.contain(extension2.packageJSON.version);
129+
expect(issueBody).to.not.contain(extension1.packageJSON.name);
130+
expect(issueBody).to.not.contain(extension1.packageJSON.publisher);
131+
expect(issueBody).to.not.contain(extension1.packageJSON.version);
132+
});
130133
});
131134
});

test/unitTests/logging/OpenURLObserver.test.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,39 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { OpenURLObserver } from "../../../src/observers/OpenURLObserver";
7-
import { vscode } from "../../../src/vscodeAdapter";
7+
import { vscode, Uri } from "../../../src/vscodeAdapter";
88
import { getFakeVsCode } from "../testAssets/Fakes";
99
import { OpenURL } from "../../../src/omnisharp/loggingEvents";
1010
import { expect } from "chai";
1111

1212
suite(`${OpenURLObserver.name}`, () => {
1313
let observer: OpenURLObserver;
1414
let vscode: vscode;
15-
let commands: Array<string>;
1615
let valueToBeParsed: string;
1716
const url = "someUrl";
17+
let openExternalCalled: boolean;
1818

1919
setup(() => {
2020
vscode = getFakeVsCode();
21-
commands = [];
21+
openExternalCalled = false;
2222
valueToBeParsed = undefined;
23-
vscode.commands.executeCommand = (command: string, ...rest: any[]) => {
24-
commands.push(command);
23+
vscode.env.openExternal = (target: Uri) => {
24+
openExternalCalled = true;
2525
return undefined;
2626
};
27+
2728
vscode.Uri.parse = (value: string) => {
2829
valueToBeParsed = value;
2930
return undefined;
3031
};
31-
observer = new OpenURLObserver(vscode);
32-
});
3332

34-
test("Execute command is called with the vscode.open command", () => {
35-
let event = new OpenURL(url);
36-
observer.post(event);
37-
expect(commands).to.be.deep.equal(["vscode.open"]);
33+
observer = new OpenURLObserver(vscode);
3834
});
3935

40-
test("url is passed to the rest parameter in executeCommand via vscode.uri.parse ", () => {
36+
test("openExternal function is called and the url is passed through the vscode.Uri.parse function", () => {
4137
let event = new OpenURL(url);
4238
observer.post(event);
4339
expect(valueToBeParsed).to.be.equal(url);
40+
expect(openExternalCalled).to.be.true;
4441
});
4542
});

test/unitTests/testAssets/Fakes.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export function getFakeVsCode(): vscode.vscode {
110110
},
111111
window: {
112112
activeTextEditor: undefined,
113-
showInformationMessage: <T extends MessageItem>(message: string, ...items: T[]) => {
113+
showInformationMessage: <T extends MessageItem>(message: string, ...items: T[]) => {
114114
throw new Error("Not Implemented");
115115
},
116116
showWarningMessage: <T extends MessageItem>(message: string, ...items: T[]) => {
@@ -145,7 +145,25 @@ export function getFakeVsCode(): vscode.vscode {
145145
throw new Error("Not Implemented");
146146
}
147147
},
148-
version: ""
148+
version: "",
149+
env: {
150+
appName: null,
151+
appRoot: null,
152+
language: null,
153+
clipboard: {
154+
writeText: () => {
155+
throw new Error("Not Implemented");
156+
},
157+
readText: () => {
158+
throw new Error("Not Implemented");
159+
}
160+
},
161+
machineId: null,
162+
sessionId: null,
163+
openExternal: () => {
164+
throw new Error("Not Implemented");
165+
}
166+
}
149167
};
150168
}
151169

@@ -162,23 +180,20 @@ export function getWorkspaceInformationUpdated(msbuild: protocol.MsBuildWorkspac
162180
};
163181

164182
return new WorkspaceInformationUpdated(a);
165-
}
183+
}
166184

167185
export function getVSCodeWithConfig() {
168186
const vscode = getFakeVsCode();
169187

170188
const _omnisharpConfig = getWorkspaceConfiguration();
171189
const _csharpConfig = getWorkspaceConfiguration();
172190

173-
vscode.workspace.getConfiguration = (section?, resource?) =>
174-
{
175-
if (section === 'omnisharp')
176-
{
191+
vscode.workspace.getConfiguration = (section?, resource?) => {
192+
if (section === 'omnisharp') {
177193
return _omnisharpConfig;
178194
}
179195

180-
if (section === 'csharp')
181-
{
196+
if (section === 'csharp') {
182197
return _csharpConfig;
183198
}
184199

0 commit comments

Comments
 (0)