Skip to content

Commit f5f7fa1

Browse files
WardenGnawpieandcakes
authored andcommitted
CompletionItemProvider for launch.json (#1401)
* Creating CompletionItemProvider for launch.json * Address PR issues * Adding jsonc-paser to ThirdPartyNotice
1 parent 9a83d3e commit f5f7fa1

File tree

6 files changed

+138
-82
lines changed

6 files changed

+138
-82
lines changed

Extension/ThirdPartyNotices.txt

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,32 @@ Microsoft C/C++ Extension for Visual Studio Code incorporates components from th
1515
9. Guidelines Support Library (https://github.com/Microsoft/GSL)
1616
10. http-proxy-agent (https://github.com/TooTallNate/node-http-proxy-agent)
1717
11. https-proxy-agent (https://github.com/TooTallNate/node-https-proxy-agent)
18-
12. libc++ (https://libcxx.llvm.org/index.html)
19-
13. LLDB (https://lldb.llvm.org/)
20-
14. LLVM (http://llvm.org/)
21-
15. MI Debug Engine (https://github.com/Microsoft/MIEngine)
22-
16. Minimatch (https://github.com/isaacs/minimatch)
23-
17. minimist (https://github.com/substack/minimist)
24-
18. mkdirp (https://github.com/substack/node-mkdirp)
25-
19. Mono (https://github.com/mono/mono)
26-
20. ms (https://github.com/rauchg/ms.js)
27-
21. msgpack for C/C++ (https://github.com/msgpack/msgpack-c)
28-
22. node-http-proxy-agent (https://github.com/TooTallNate/node-https-proxy-agent)
29-
23. node-https-proxy-agent (https://github.com/TooTallNate/node-https-proxy-agent)
30-
24. os-tmpdir (https://github.com/sindresorhus/os-tmpdir)
31-
25. Pend (https://github.com/andrewrk/node-pend)
32-
26. pevents (https://github.com/neosmart/pevents)
33-
27. RapidJSON (https://github.com/miloyip/rapidjson)
34-
28. semver (https://github.com/npm/node-semver)
35-
29. SQLite (https://www.sqlite.org/)
18+
12. jsonc-parser (https://github.com/Microsoft/node-jsonc-parser)
19+
13. libc++ (https://libcxx.llvm.org/index.html)
20+
14. LLDB (https://lldb.llvm.org/)
21+
15. LLVM (http://llvm.org/)
22+
16. MI Debug Engine (https://github.com/Microsoft/MIEngine)
23+
17. Minimatch (https://github.com/isaacs/minimatch)
24+
18. minimist (https://github.com/substack/minimist)
25+
19. mkdirp (https://github.com/substack/node-mkdirp)
26+
20. Mono (https://github.com/mono/mono)
27+
21. ms (https://github.com/rauchg/ms.js)
28+
22. msgpack for C/C++ (https://github.com/msgpack/msgpack-c)
29+
23. node-http-proxy-agent (https://github.com/TooTallNate/node-https-proxy-agent)
30+
24. node-https-proxy-agent (https://github.com/TooTallNate/node-https-proxy-agent)
31+
25. os-tmpdir (https://github.com/sindresorhus/os-tmpdir)
32+
26. Pend (https://github.com/andrewrk/node-pend)
33+
27. pevents (https://github.com/neosmart/pevents)
34+
28. RapidJSON (https://github.com/miloyip/rapidjson)
35+
29. semver (https://github.com/npm/node-semver)
36+
30. SQLite (https://www.sqlite.org/)
3637
Includes:functions (from fossil) (https://fossil-scm.org)
37-
30. Tmp (https://github.com/raszi/node-tmp)
38+
31. Tmp (https://github.com/raszi/node-tmp)
3839
Includes:sample code (https://blog.tompawlak.org/generate-random-values-nodejs-javascript)
39-
31. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node)
40-
32. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry)
41-
33. vscode-languageserver-node (https://github.com/Microsoft/vscode-languageserver-node)
42-
34. yauzl (https://github.com/thejoshwolfe/yauzl)
40+
32. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node)
41+
33. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry)
42+
34. vscode-languageserver-node (https://github.com/Microsoft/vscode-languageserver-node)
43+
35. yauzl (https://github.com/thejoshwolfe/yauzl)
4344

4445

4546
%% agent-base NOTICES AND INFORMATION BEGIN HERE
@@ -500,6 +501,32 @@ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
500501
=========================================
501502
END OF https-proxy-agent NOTICES AND INFORMATION
502503

504+
%% jsonc-parser NOTICES AND INFORMATION BEGIN HERE
505+
=========================================
506+
(MIT License)
507+
508+
Copyright 2016, Microsoft
509+
510+
ermission is hereby granted, free of charge, to any person obtaining a copy
511+
of this software and associated documentation files (the "Software"), to deal
512+
in the Software without restriction, including without limitation the rights
513+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
514+
of the Software, and to permit persons to whom the Software is furnished to do
515+
so, subject to the following conditions:
516+
517+
The above copyright notice and this permission notice shall be included in all
518+
copies or substantial portions of the Software.
519+
520+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
521+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
522+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
523+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
524+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
525+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
526+
THE SOFTWARE.
527+
=========================================
528+
END OF jsonc-parser NOTICES AND INFORMATION
529+
503530
%% libc++ NOTICES AND INFORMATION BEGIN HERE
504531
=========================================
505532
The libc++ library is dual licensed under both the University of Illinois

Extension/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,7 @@
10321032
"@types/mocha": "^2.2.43"
10331033
},
10341034
"dependencies": {
1035+
"jsonc-parser": "^1.0.0",
10351036
"vscode-languageclient": "~3.4.5",
10361037
"vscode-extension-telemetry": "~0.0.8",
10371038
"http-proxy-agent": "~2.0.0",

Extension/src/Debugger/configurationProvider.ts

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
* See 'LICENSE' in the project root for license information.
44
* ------------------------------------------------------------------------------------------ */
55

6-
import * as os from 'os';
6+
import * as os from 'os';
77
import * as path from 'path';
88
import * as fs from 'fs';
99
import * as vscode from 'vscode';
1010
import {indentJsonString, IConfiguration, IConfigurationSnippet, DebuggerType, MIConfigurations, WindowsConfigurations, WSLConfigurations, PipeTransportConfigurations } from './configurations';
1111
import * as util from '../common';
12+
import { parse } from 'jsonc-parser';
1213

1314
abstract class CppConfigurationProvider implements vscode.DebugConfigurationProvider {
1415
private type: DebuggerType;
@@ -54,7 +55,7 @@ export class CppDbgConfigurationProvider extends CppConfigurationProvider {
5455

5556
export interface IConfigurationAssetProvider {
5657
getInitialConfigurations(debuggerType: DebuggerType): any;
57-
getConfigurationSnippets(): string;
58+
getConfigurationSnippets(): vscode.CompletionItem[];
5859
}
5960

6061
export class ConfigurationAssetProviderFactory {
@@ -80,7 +81,7 @@ abstract class DefaultConfigurationProvider implements IConfigurationAssetProvid
8081

8182
// Only launch configurations are initial configurations
8283
this.configurations.forEach(configuration => {
83-
configurationSnippet.push(configuration.GetLaunchConfiguration(true));
84+
configurationSnippet.push(configuration.GetLaunchConfiguration());
8485
});
8586

8687
let initialConfigurations = configurationSnippet.filter(snippet => snippet.debuggerType == debuggerType && snippet.isInitialConfiguration)
@@ -90,30 +91,15 @@ abstract class DefaultConfigurationProvider implements IConfigurationAssetProvid
9091
return initialConfigurations;
9192
}
9293

93-
// TODO: Update this function when VsCode enables an API for configuration snippet providers
94-
public getConfigurationSnippets(): string {
95-
if (util.packageJson.contributes.debuggers[0] && !util.packageJson.contributes.debuggers[0].configurationSnippets) {
96-
let configurationSnippet: IConfigurationSnippet[] = [];
94+
public getConfigurationSnippets(): vscode.CompletionItem[] {
95+
let completionItems: vscode.CompletionItem[] = [];
9796

98-
this.configurations.forEach(configuration => {
99-
configurationSnippet.push(configuration.GetLaunchConfiguration(false));
100-
configurationSnippet.push(configuration.GetAttachConfiguration());
101-
});
102-
103-
util.packageJson.contributes.debuggers[0].configurationSnippets = configurationSnippet.map(snippet => {
104-
// Remove internal fields
105-
delete snippet.isInitialConfiguration;
106-
delete snippet.debuggerType;
107-
108-
return snippet;
109-
});
110-
111-
fs.writeFileSync(util.getPackageJsonPath(), util.getPackageJsonString());
112-
util.enableReloadOrWaitPrompt();
113-
util.touchExtensionFolder(); // Required to avoid package.json caching.
114-
}
97+
this.configurations.forEach(configuration => {
98+
completionItems.push(convertConfigurationSnippetToCompetionItem(configuration.GetLaunchConfiguration()));
99+
completionItems.push(convertConfigurationSnippetToCompetionItem(configuration.GetAttachConfiguration()));
100+
});
115101

116-
return ""; // TODO
102+
return completionItems;
117103
}
118104
}
119105

@@ -173,4 +159,49 @@ class LinuxConfigurationProvider extends DefaultConfigurationProvider {
173159
new PipeTransportConfigurations(this.MIMode, this.executable, this.pipeProgram, this.setupCommandsBlock)
174160
]
175161
}
162+
}
163+
164+
function convertConfigurationSnippetToCompetionItem(snippet: IConfigurationSnippet): vscode.CompletionItem {
165+
var item: vscode.CompletionItem = new vscode.CompletionItem(snippet.label, vscode.CompletionItemKind.Snippet);
166+
167+
item.insertText = snippet.bodyText;
168+
169+
return item;
170+
}
171+
172+
export class ConfigurationSnippetProvider implements vscode.CompletionItemProvider {
173+
private provider: IConfigurationAssetProvider;
174+
private snippets: vscode.CompletionItem[];
175+
176+
constructor(provider: IConfigurationAssetProvider) {
177+
this.provider = provider;
178+
this.snippets = this.provider.getConfigurationSnippets();
179+
}
180+
public resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): Thenable<vscode.CompletionItem> {
181+
return Promise.resolve(item);
182+
}
183+
184+
// This function will only provide completion items via the Add Configuration Button
185+
// There are two cases where the configuration array has nothing or has some items.
186+
// 1. If it has nothing, insert a snippet the user selected.
187+
// 2. If there are items, the Add Configuration button will append it to the start of the configuration array. This function inserts a comma at the end of the snippet.
188+
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList> {
189+
let items: vscode.CompletionItem[] = this.snippets;
190+
191+
const launch: any = parse(document.getText());
192+
// Check to see if the array is empty, so any additional inserted snippets will need commas.
193+
if (launch.configurations.length !== 0)
194+
{
195+
items = [];
196+
197+
// Make a copy of each snippet since we are adding a comma to the end of the insertText.
198+
this.snippets.forEach((item) => items.push(Object.assign({}, item)));
199+
200+
items.map((item) => {
201+
item.insertText = item.insertText + ',' // Add comma
202+
});
203+
}
204+
205+
return Promise.resolve(new vscode.CompletionList(items, true));
206+
}
176207
}

Extension/src/Debugger/configurations.ts

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,14 @@ function formatString(format: string, args: string[]) {
3131
return format;
3232
}
3333

34-
// Initial configurations do not require escaping ${keyword}. If it is being used
35-
// as a configuration snippet, then ${keyword} will need to be escaped or VsCode will
36-
// try to evaluate it.
37-
function EnsureTokensEscapedInLaunchJsonMacro(keyword: string, isInitialConfiguration: boolean): string {
38-
if (isInitialConfiguration) {
39-
return "${" + keyword + "}";
40-
}
41-
else {
42-
return "\\$\{" + keyword + "\}";
43-
}
44-
}
45-
46-
function CreateLaunchString(name: string, type: string, executable: string, isInitialConfiguration: boolean): string {
34+
function CreateLaunchString(name: string, type: string, executable: string): string {
4735
return `"name": "${name}",
4836
"type": "${type}",
4937
"request": "launch",
50-
"program": "${"enter program name, for example " + EnsureTokensEscapedInLaunchJsonMacro("workspaceFolder", isInitialConfiguration) + "/" + executable}",
38+
"program": "${"enter program name, for example " + "$\{workspaceFolder\}" + "/" + executable}",
5139
"args": [],
5240
"stopAtEntry": false,
53-
"cwd": \"${EnsureTokensEscapedInLaunchJsonMacro("workspaceFolder", isInitialConfiguration)}\",
41+
"cwd": "$\{workspaceFolder\}",
5442
"environment": [],
5543
"externalConsole": true
5644
`
@@ -61,17 +49,17 @@ function CreateAttachString(name: string, type: string, executable: string): str
6149
"name": "${name}",
6250
"type": "${type}",
6351
"request": "attach",{0}
64-
"processId": \"\\$\{command:pickProcess\}\"
65-
`, [type === "cppdbg" ? `${os.EOL}"program": "${"enter program name, for example \\$\{workspaceFolder\}/" + executable}",` : ""]);
52+
"processId": "$\{command:pickProcess\}"
53+
`, [type === "cppdbg" ? `${os.EOL}"program": "${"enter program name, for example $\{workspaceFolder\}/" + executable}",` : ""]);
6654
}
6755

6856
function CreateRemoteAttachString(name: string, type: string, executable: string): string {
6957
return `
7058
"name": "${name}",
7159
"type": "${type}",
7260
"request": "attach",
73-
"program": "${"enter program name, for example \\$\{workspaceFolder\}/" + executable}",
74-
"processId": \"\\$\{command:pickRemoteProcess\}\"
61+
"program": "${"enter program name, for example $\{workspaceFolder\}/" + executable}",
62+
"processId": "$\{command:pickRemoteProcess\}"
7563
`;
7664
}
7765

@@ -86,7 +74,7 @@ function CreateRemoteAttachString(name: string, type: string, executable: string
8674
}
8775

8876
export interface IConfiguration {
89-
GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet;
77+
GetLaunchConfiguration(): IConfigurationSnippet;
9078
GetAttachConfiguration(): IConfigurationSnippet;
9179
}
9280

@@ -108,17 +96,17 @@ abstract class Configuration implements IConfiguration {
10896
this.additionalProperties = additionalProperties;
10997
}
11098

111-
abstract GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet;
99+
abstract GetLaunchConfiguration(): IConfigurationSnippet;
112100
abstract GetAttachConfiguration(): IConfigurationSnippet;
113101
}
114102

115103
export class MIConfigurations extends Configuration {
116104

117-
public GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet {
105+
public GetLaunchConfiguration(): IConfigurationSnippet {
118106
let name: string = `(${this.MIMode}) Launch`;
119107

120108
let body: string = formatString(`{
121-
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable, isInitialConfiguration))},
109+
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable))},
122110
\t"MIMode": "${this.MIMode}"{0}{1}
123111
}`, [this.miDebugger === "cppdbg" && os.platform() === "win32" ? `,${os.EOL}\t"miDebuggerPath": "/path/to/gdb"` : "",
124112
this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
@@ -152,12 +140,12 @@ this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalPrope
152140

153141
export class PipeTransportConfigurations extends Configuration {
154142

155-
public GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet {
143+
public GetLaunchConfiguration(): IConfigurationSnippet {
156144
let name: string = `(${this.MIMode}) Pipe Launch`;
157145

158146
let body: string = formatString(`
159147
{
160-
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable, isInitialConfiguration))},
148+
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable))},
161149
\t${indentJsonString(CreatePipeTransportString(this.pipeProgram, this.MIMode))},
162150
\t"MIMode": "${this.MIMode}"{0}
163151
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
@@ -192,12 +180,12 @@ export class PipeTransportConfigurations extends Configuration {
192180

193181
export class WindowsConfigurations extends Configuration {
194182

195-
public GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet {
183+
public GetLaunchConfiguration(): IConfigurationSnippet {
196184
let name = "(Windows) Launch";
197185

198186
let body: string = `
199187
{
200-
\t${indentJsonString(CreateLaunchString(name, this.windowsDebugger, this.executable, isInitialConfiguration))}
188+
\t${indentJsonString(CreateLaunchString(name, this.windowsDebugger, this.executable))}
201189
}`;
202190

203191
return {
@@ -229,14 +217,14 @@ export class WindowsConfigurations extends Configuration {
229217
}
230218

231219
export class WSLConfigurations extends Configuration {
232-
public bashPipeProgram = "C:\\\\\\\\Windows\\\\\\\\sysnative\\\\\\\\bash.exe";
220+
public bashPipeProgram = "C:\\\\Windows\\\\sysnative\\\\bash.exe";
233221

234-
public GetLaunchConfiguration(isInitialConfiguration: boolean): IConfigurationSnippet {
222+
public GetLaunchConfiguration(): IConfigurationSnippet {
235223
let name: string = `(${this.MIMode}) Bash on Windows Launch`;
236224

237225
let body: string = formatString(`
238226
{
239-
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable, isInitialConfiguration))},
227+
\t${indentJsonString(CreateLaunchString(name, this.miDebugger, this.executable))},
240228
\t${indentJsonString(CreatePipeTransportString(this.bashPipeProgram, this.MIMode))}{0}
241229
}`, [this.additionalProperties ? `,${os.EOL}\t${indentJsonString(this.additionalProperties)}` : ""]);
242230

Extension/src/Debugger/extension.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
77
import * as os from 'os';
88
import { AttachPicker, RemoteAttachPicker } from './attachToProcess';
99
import { NativeAttachItemsProviderFactory } from './nativeAttach';
10-
import { ConfigurationAssetProviderFactory, CppVsDbgConfigurationProvider, CppDbgConfigurationProvider } from './configurationProvider';
10+
import { ConfigurationAssetProviderFactory, CppVsDbgConfigurationProvider, CppDbgConfigurationProvider, ConfigurationSnippetProvider } from './configurationProvider';
1111
import { DebuggerType } from './configurations';
1212
import * as util from '../common';
1313
import * as path from 'path';
@@ -35,11 +35,20 @@ export function initialize() {
3535

3636
configurationProvider.getConfigurationSnippets();
3737

38+
const launchJsonDocumentSelector: vscode.DocumentSelector = [{
39+
language: 'jsonc',
40+
pattern: '**/launch.json'
41+
}];
42+
// ConfigurationSnippetProvider needs to be initiallized after configurationProvider calls getConfigurationSnippets.
43+
disposables.push(vscode.languages.registerCompletionItemProvider(launchJsonDocumentSelector, new ConfigurationSnippetProvider(configurationProvider)));
44+
3845
disposables.push(vscode.window.onDidChangeActiveTextEditor(onDidChangeActiveTextEditor));
3946
onDidChangeActiveTextEditor(vscode.window.activeTextEditor);
4047

41-
// Activate Adapter Commands
42-
registerAdapterExecutableCommands();
48+
// Activate Adapter Commands
49+
registerAdapterExecutableCommands();
50+
51+
vscode.Disposable.from(...disposables);
4352
}
4453

4554
export function dispose(): void {

Extension/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,4 +452,4 @@ function rewriteManifest(installBlob: InstallBlob): void {
452452
"onCommand:C_Cpp.TakeSurvey",
453453
"onDebug"
454454
];
455-
}
455+
}

0 commit comments

Comments
 (0)