diff --git a/Extension/package.json b/Extension/package.json index 28b64b4da2..e7c0172c99 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -510,8 +510,13 @@ "category": "C/C++" }, { - "command": "C_Cpp.ConfigurationEdit", - "title": "%c_cpp.command.configurationEdit.title%", + "command": "C_Cpp.ConfigurationEditJSON", + "title": "%c_cpp.command.configurationEditJSON.title%", + "category": "C/C++" + }, + { + "command": "C_Cpp.ConfigurationEditUI", + "title": "%c_cpp.command.configurationEditUI.title%", "category": "C/C++" }, { diff --git a/Extension/package.nls.it.json b/Extension/package.nls.it.json index 39c33ea727..46a289ee33 100644 --- a/Extension/package.nls.it.json +++ b/Extension/package.nls.it.json @@ -1,7 +1,8 @@ { "c_cpp.command.configurationSelect.title": "Scegli una configurazione...", "c_cpp.command.configurationProviderSelect.title": "Cambia provider configurazioni...", - "c_cpp.command.configurationEdit.title": "Modifica configurazioni...", + "c_cpp.command.configurationEditJSON.title": "Modifica configurazioni... (JSON)", + "c_cpp.command.configurationEditUI.title": "Modifica configurazioni... (UI)", "c_cpp.command.goToDeclaration.title": "Vai a dichiarazione", "c_cpp.command.peekDeclaration.title": "Visualizza dichiarazione", "c_cpp.command.switchHeaderSource.title": "Visualizza Header/Sorgente", diff --git a/Extension/package.nls.json b/Extension/package.nls.json index 97722175b4..945dd73603 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -1,7 +1,8 @@ { "c_cpp.command.configurationSelect.title": "Select a configuration...", "c_cpp.command.configurationProviderSelect.title": "Change configuration provider...", - "c_cpp.command.configurationEdit.title": "Edit configurations...", + "c_cpp.command.configurationEditJSON.title": "Edit configurations... (JSON)", + "c_cpp.command.configurationEditUI.title": "Edit configurations... (UI)", "c_cpp.command.goToDeclaration.title": "Go to Declaration", "c_cpp.command.peekDeclaration.title": "Peek Declaration", "c_cpp.command.switchHeaderSource.title": "Switch Header/Source", diff --git a/Extension/package.nls.zh-cn.json b/Extension/package.nls.zh-cn.json index 0b10276adc..678de8a678 100644 --- a/Extension/package.nls.zh-cn.json +++ b/Extension/package.nls.zh-cn.json @@ -1,7 +1,8 @@ { "c_cpp.command.configurationSelect.title": "选择配置...", "c_cpp.command.configurationProviderSelect.title": "更换配置提供程序...", - "c_cpp.command.configurationEdit.title": "编辑配置...", + "c_cpp.command.configurationEditJSON.title": "编辑配置... (JSON)", + "c_cpp.command.configurationEditUI.title": "编辑配置... (UI)", "c_cpp.command.goToDeclaration.title": "跳转到声明", "c_cpp.command.peekDeclaration.title": "查看声明", "c_cpp.command.switchHeaderSource.title": "切换头文件/源文件", diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index beee5f3330..e30b769ad3 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -252,6 +252,8 @@ export interface Client { handleConfigurationProviderSelectCommand(): void; handleShowParsingCommands(): void; handleConfigurationEditCommand(): void; + handleConfigurationEditJSONCommand(): void; + handleConfigurationEditUICommand(): void; handleAddToIncludePathCommand(path: string): void; onInterval(): void; dispose(): Thenable; @@ -1004,22 +1006,31 @@ class DefaultClient implements Client { let showIntelliSenseFallbackMessage: PersistentState = new PersistentState("CPP.showIntelliSenseFallbackMessage", true); if (showIntelliSenseFallbackMessage.Value) { ui.showConfigureIncludePathMessage(() => { - let learnMorePanel: string = "Configuration Help"; + let configJSON: string = "Configure (JSON)"; + let configUI: string = "Configure (UI)"; let dontShowAgain: string = "Don't Show Again"; let fallbackMsg: string = this.configuration.VcpkgInstalled ? "Update your IntelliSense settings or use Vcpkg to install libraries to help find missing headers." : "Configure your IntelliSense settings to help find missing headers."; - return vscode.window.showInformationMessage(fallbackMsg, learnMorePanel, dontShowAgain).then((value) => { + return vscode.window.showInformationMessage(fallbackMsg, configJSON, configUI, dontShowAgain).then((value) => { switch (value) { - case learnMorePanel: - let uri: vscode.Uri = vscode.Uri.parse(`https://go.microsoft.com/fwlink/?linkid=864631`); - vscode.commands.executeCommand('vscode.open', uri); + case configJSON: + vscode.commands.getCommands(true).then((commands: string[]) => { + if (commands.indexOf("workbench.action.problems.focus") >= 0) { + vscode.commands.executeCommand("workbench.action.problems.focus"); + } + }); + this.handleConfigurationEditJSONCommand(); + telemetry.logLanguageServerEvent("SettingsCommand", { "toast": "json" }, null); + break; + case configUI: vscode.commands.getCommands(true).then((commands: string[]) => { if (commands.indexOf("workbench.action.problems.focus") >= 0) { vscode.commands.executeCommand("workbench.action.problems.focus"); - } + } }); - this.handleConfigurationEditCommand(); + this.handleConfigurationEditUICommand(); + telemetry.logLanguageServerEvent("SettingsCommand", { "toast": "ui" }, null); break; case dontShowAgain: showIntelliSenseFallbackMessage.Value = false; @@ -1365,6 +1376,14 @@ class DefaultClient implements Client { this.notifyWhenReady(() => this.configuration.handleConfigurationEditCommand(vscode.window.showTextDocument)); } + public handleConfigurationEditJSONCommand(): void { + this.notifyWhenReady(() => this.configuration.handleConfigurationEditJSONCommand(vscode.window.showTextDocument)); + } + + public handleConfigurationEditUICommand(): void { + this.notifyWhenReady(() => this.configuration.handleConfigurationEditUICommand(vscode.window.showTextDocument)); + } + public handleAddToIncludePathCommand(path: string): void { this.notifyWhenReady(() => this.configuration.addToIncludePathCommand(path)); } @@ -1448,6 +1467,8 @@ class NullClient implements Client { handleConfigurationProviderSelectCommand(): void {} handleShowParsingCommands(): void {} handleConfigurationEditCommand(): void {} + handleConfigurationEditJSONCommand(): void {} + handleConfigurationEditUICommand(): void {} handleAddToIncludePathCommand(path: string): void {} onInterval(): void {} dispose(): Thenable { diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 2c7fc21426..fd5e405dbe 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -10,9 +10,10 @@ import * as vscode from 'vscode'; import * as util from '../common'; import * as telemetry from '../telemetry'; import { PersistentFolderState } from './persistentState'; -import { CppSettings } from './settings'; +import { CppSettings, OtherSettings } from './settings'; import { ABTestSettings, getABTestSettings } from '../abTesting'; import { getCustomConfigProviders } from './customProviders'; +import { SettingsPanel, ViewStateEvent } from './settingsPanel'; import * as os from 'os'; import escapeStringRegExp = require('escape-string-regexp'); const configVersion: number = 4; @@ -113,6 +114,7 @@ export class CppProperties { private diagnosticCollection: vscode.DiagnosticCollection; private prevSquiggleMetrics: Map = new Map(); private rootfs: string = null; + private settingsPanel: SettingsPanel = undefined; // Any time the default settings are parsed and assigned to `this.configurationJson`, // we want to track when the default includes have been added to it. @@ -125,9 +127,7 @@ export class CppProperties { this.currentConfigurationIndex = new PersistentFolderState("CppProperties.currentConfigurationIndex", -1, rootPath); this.configFolder = path.join(rootPath, ".vscode"); this.diagnosticCollection = vscode.languages.createDiagnosticCollection(rootPath); - this.buildVcpkgIncludePath(); - this.disposables.push(vscode.Disposable.from(this.configurationsChanged, this.selectionChanged, this.compileCommandsChanged)); } @@ -360,7 +360,7 @@ export class CppProperties { public addToIncludePathCommand(path: string): void { this.handleConfigurationEditCommand((document: vscode.TextDocument) => { telemetry.logLanguageServerEvent("addToIncludePath"); - this.parsePropertiesFile(); // Clear out any modifications we may have made internally. + this.parsePropertiesFileAndHandleSquiggles(); // Clear out any modifications we may have made internally. let config: Configuration = this.CurrentConfiguration; if (config.includePath === undefined) { config.includePath = ["${default}"]; @@ -375,7 +375,7 @@ export class CppProperties { return new Promise((resolve) => { if (this.propertiesFile) { this.handleConfigurationEditCommand((document: vscode.TextDocument) => { - this.parsePropertiesFile(); // Clear out any modifications we may have made internally. + this.parsePropertiesFileAndHandleSquiggles(); // Clear out any modifications we may have made internally. let config: Configuration = this.CurrentConfiguration; if (providerId) { config.configurationProvider = providerId; @@ -401,7 +401,7 @@ export class CppProperties { public setCompileCommands(path: string): void { this.handleConfigurationEditCommand((document: vscode.TextDocument) => { - this.parsePropertiesFile(); // Clear out any modifications we may have made internally. + this.parsePropertiesFileAndHandleSquiggles(); // Clear out any modifications we may have made internally. let config: Configuration = this.CurrentConfiguration; config.compileCommands = path; fs.writeFileSync(this.propertiesFile.fsPath, JSON.stringify(this.configurationJson, null, 4)); @@ -550,49 +550,106 @@ export class CppProperties { } } - public handleConfigurationEditCommand(onSuccess: (document: vscode.TextDocument) => void): void { + private async ensurePropertiesFile(): Promise { if (this.propertiesFile && fs.existsSync(this.propertiesFile.fsPath)) { + return; + } else { + try { + if (!fs.existsSync(this.configFolder)) { + fs.mkdirSync(this.configFolder); + } + + let fullPathToFile: string = path.join(this.configFolder, "c_cpp_properties.json"); + let filePath: vscode.Uri = vscode.Uri.file(fullPathToFile).with({ scheme: "untitled" }); + + let document: vscode.TextDocument = await vscode.workspace.openTextDocument(filePath); + + let edit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit(); + if (this.configurationJson) { + this.resetToDefaultSettings(true); + } + this.applyDefaultIncludePathsAndFrameworks(); + let settings: CppSettings = new CppSettings(this.rootUri); + if (settings.defaultConfigurationProvider) { + this.configurationJson.configurations.forEach(config => { + config.configurationProvider = settings.defaultConfigurationProvider; + }); + settings.update("default.configurationProvider", undefined); // delete the setting + } + let savedKnownCompilers: KnownCompiler[] = this.configurationJson.configurations[0].knownCompilers; + delete this.configurationJson.configurations[0].knownCompilers; + + edit.insert(document.uri, new vscode.Position(0, 0), JSON.stringify(this.configurationJson, null, 4)); + + this.configurationJson.configurations[0].knownCompilers = savedKnownCompilers; + await vscode.workspace.applyEdit(edit); + + // Fix for issue 163 + // https://github.com/Microsoft/vscppsamples/issues/163 + // Save the file to disk so that when the user tries to re-open the file it exists. + // Before this fix the file existed but was unsaved, so we went through the same + // code path and reapplied the edit. + await document.save(); + + this.propertiesFile = vscode.Uri.file(path.join(this.configFolder, "c_cpp_properties.json")); + + } catch (err) { + vscode.window.showErrorMessage(`Failed to create "${this.configFolder}": ${err.message}`); + } + } + return; + } + + public handleConfigurationEditCommand(onSuccess: (document: vscode.TextDocument) => void): void { + let otherSettings: OtherSettings = new OtherSettings(this.rootUri); + if (otherSettings.settingsEditor === "ui") { + this.handleConfigurationEditUICommand(onSuccess); + } else { + this.handleConfigurationEditJSONCommand(onSuccess); + } + } + + public handleConfigurationEditJSONCommand(onSuccess: (document: vscode.TextDocument) => void): void { + this.ensurePropertiesFile().then(() => { + console.assert(this.propertiesFile); + // Directly open the json file vscode.workspace.openTextDocument(this.propertiesFile).then((document: vscode.TextDocument) => { onSuccess(document); }); - } else { - fs.mkdir(this.configFolder, (e: NodeJS.ErrnoException) => { - if (!e || e.code === 'EEXIST') { - let fullPathToFile: string = path.join(this.configFolder, "c_cpp_properties.json"); - let filePath: vscode.Uri = vscode.Uri.file(fullPathToFile).with({ scheme: "untitled" }); - vscode.workspace.openTextDocument(filePath).then((document: vscode.TextDocument) => { - let edit: vscode.WorkspaceEdit = new vscode.WorkspaceEdit(); - if (this.configurationJson) { - this.resetToDefaultSettings(true); - } - this.applyDefaultIncludePathsAndFrameworks(); - let settings: CppSettings = new CppSettings(this.rootUri); - if (settings.defaultConfigurationProvider) { - this.configurationJson.configurations.forEach(config => { - config.configurationProvider = settings.defaultConfigurationProvider; - }); - settings.update("default.configurationProvider", undefined); // delete the setting - } - let savedKnownCompilers: KnownCompiler[] = this.configurationJson.configurations[0].knownCompilers; - delete this.configurationJson.configurations[0].knownCompilers; - edit.insert(document.uri, new vscode.Position(0, 0), JSON.stringify(this.configurationJson, null, 4)); - this.configurationJson.configurations[0].knownCompilers = savedKnownCompilers; - vscode.workspace.applyEdit(edit).then((status) => { - // Fix for issue 163 - // https://github.com/Microsoft/vscppsamples/issues/163 - // Save the file to disk so that when the user tries to re-open the file it exists. - // Before this fix the file existed but was unsaved, so we went through the same - // code path and reapplied the edit. - document.save().then(() => { - this.propertiesFile = vscode.Uri.file(path.join(this.configFolder, "c_cpp_properties.json")); - vscode.workspace.openTextDocument(this.propertiesFile).then((document: vscode.TextDocument) => { - onSuccess(document); - }); - }); - }); + }); + } + + public handleConfigurationEditUICommand(onSuccess: (document: vscode.TextDocument) => void): void { + this.ensurePropertiesFile().then(() => { + if (this.propertiesFile) { + if (this.parsePropertiesFile()) { + // Parse successful, show UI + if (this.settingsPanel === undefined) { + this.settingsPanel = new SettingsPanel(); + this.settingsPanel.SettingsPanelViewStateChanged((e) => this.onSettingsPanelViewStateChanged(e)); + this.disposables.push(this.settingsPanel); + } + this.settingsPanel.createOrShow(this.configurationJson.configurations[this.currentConfigurationIndex.Value]); + } else { + // Parse failed, open json file + vscode.workspace.openTextDocument(this.propertiesFile).then((document: vscode.TextDocument) => { + onSuccess(document); }); } - }); + } + }); + } + + private onSettingsPanelViewStateChanged(e: ViewStateEvent): void { + if (e.isActive && this.configurationJson) { + // The settings UI became visible or active. + // Ensure settingsPanel is referencing current configuration + this.settingsPanel.updateConfigUI(this.configurationJson.configurations[this.currentConfigurationIndex.Value]); + } else { + console.assert(this.configurationJson); + // The settings UI closed or is out of focus, save any changes to current configuration + // No need to get the changed values from settingsPanel because settingsPanel has a reference of this.configurationJson + fs.writeFileSync(this.propertiesFile.fsPath, JSON.stringify(this.configurationJson, null, 4)); } } @@ -602,7 +659,7 @@ export class CppProperties { } this.configFileWatcherFallbackTime = new Date(); if (this.propertiesFile) { - this.parsePropertiesFile(); + this.parsePropertiesFileAndHandleSquiggles(); // parsePropertiesFile can fail, but it won't overwrite an existing configurationJson in the event of failure. // this.configurationJson should only be undefined here if we have never successfully parsed the propertiesFile. if (this.configurationJson) { @@ -622,7 +679,14 @@ export class CppProperties { this.updateServerOnFolderSettingsChange(); } - private parsePropertiesFile(): void { + private parsePropertiesFileAndHandleSquiggles(): void { + if (this.parsePropertiesFile()) { + this.handleSquiggles(); + } + } + + private parsePropertiesFile(): boolean { + let success: boolean = true; try { let readResults: string = fs.readFileSync(this.propertiesFile.fsPath, 'utf8'); if (readResults === "") { @@ -698,31 +762,33 @@ export class CppProperties { } catch (err) { // Ignore write errors, the file may be under source control. Updated settings will only be modified in memory. vscode.window.showWarningMessage(`Attempt to update "${this.propertiesFile.fsPath}" failed (do you have write access?)`); + success = false; } } - if (this.configurationJson.enableConfigurationSquiggles === false) { - this.diagnosticCollection.clear(); - } else if (this.configurationJson.enableConfigurationSquiggles === true) { - this.handleSquiggles(); - } else { - const settings: CppSettings = new CppSettings(this.rootUri); - if (settings.defaultEnableConfigurationSquiggles === false) { - this.diagnosticCollection.clear(); - } else { - this.handleSquiggles(); - } - } } catch (err) { vscode.window.showErrorMessage(`Failed to parse "${this.propertiesFile.fsPath}": ${err.message}`); - throw err; + success = false; } + return success; } private handleSquiggles(): void { if (!this.propertiesFile) { return; } + + if (this.configurationJson.enableConfigurationSquiggles === false) { + this.diagnosticCollection.clear(); + return; + } + + const settings: CppSettings = new CppSettings(this.rootUri); + if (settings.defaultEnableConfigurationSquiggles === false) { + this.diagnosticCollection.clear(); + return; + } + vscode.workspace.openTextDocument(this.propertiesFile).then((document: vscode.TextDocument) => { let diagnostics: vscode.Diagnostic[] = new Array(); diff --git a/Extension/src/LanguageServer/extension.ts b/Extension/src/LanguageServer/extension.ts index af7358374e..a1ad7e19dd 100644 --- a/Extension/src/LanguageServer/extension.ts +++ b/Extension/src/LanguageServer/extension.ts @@ -664,7 +664,8 @@ export function registerCommands(): void { disposables.push(vscode.commands.registerCommand('C_Cpp.ResetDatabase', onResetDatabase)); disposables.push(vscode.commands.registerCommand('C_Cpp.ConfigurationSelect', onSelectConfiguration)); disposables.push(vscode.commands.registerCommand('C_Cpp.ConfigurationProviderSelect', onSelectConfigurationProvider)); - disposables.push(vscode.commands.registerCommand('C_Cpp.ConfigurationEdit', onEditConfiguration)); + disposables.push(vscode.commands.registerCommand('C_Cpp.ConfigurationEditJSON', onEditConfigurationJSON)); + disposables.push(vscode.commands.registerCommand('C_Cpp.ConfigurationEditUI', onEditConfigurationUI)); disposables.push(vscode.commands.registerCommand('C_Cpp.AddToIncludePath', onAddToIncludePath)); disposables.push(vscode.commands.registerCommand('C_Cpp.EnableErrorSquiggles', onEnableSquiggles)); disposables.push(vscode.commands.registerCommand('C_Cpp.DisableErrorSquiggles', onDisableSquiggles)); @@ -790,12 +791,23 @@ function onSelectConfigurationProvider(): void { } } -function onEditConfiguration(): void { +function onEditConfigurationJSON(): void { onActivationEvent(); + telemetry.logLanguageServerEvent("SettingsCommand", { "palette": "json" }, null); if (!isFolderOpen()) { vscode.window.showInformationMessage('Open a folder first to edit configurations'); } else { - selectClient().then(client => client.handleConfigurationEditCommand(), rejected => {}); + selectClient().then(client => client.handleConfigurationEditJSONCommand(), rejected => {}); + } +} + +function onEditConfigurationUI(): void { + onActivationEvent(); + telemetry.logLanguageServerEvent("SettingsCommand", { "palette": "ui" }, null); + if (!isFolderOpen()) { + vscode.window.showInformationMessage('Open a folder first to edit configurations'); + } else { + selectClient().then(client => client.handleConfigurationEditUICommand(), rejected => {}); } } diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index c3f37d772a..0d33673701 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -97,6 +97,7 @@ export class OtherSettings { public get filesAssociations(): any { return vscode.workspace.getConfiguration("files", null).get("associations"); } public get filesExclude(): vscode.WorkspaceConfiguration { return vscode.workspace.getConfiguration("files", this.resource).get("exclude"); } public get searchExclude(): vscode.WorkspaceConfiguration { return vscode.workspace.getConfiguration("search", this.resource).get("exclude"); } + public get settingsEditor(): string { return vscode.workspace.getConfiguration("workbench.settings").get("editor"); } public set filesAssociations(value: any) { vscode.workspace.getConfiguration("files", null).update("associations", value, vscode.ConfigurationTarget.Workspace); diff --git a/Extension/src/LanguageServer/settingsPanel.ts b/Extension/src/LanguageServer/settingsPanel.ts new file mode 100644 index 0000000000..adaa8897fc --- /dev/null +++ b/Extension/src/LanguageServer/settingsPanel.ts @@ -0,0 +1,224 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import * as path from 'path'; +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as util from '../common'; +import * as config from './configurations'; + +// TODO: share ElementId between SettingsPanel and SettingsApp. Investigate why SettingsApp cannot import/export +const elementId: { [key: string]: string } = { + activeConfig: "activeConfig", + compilerPath: "compilerPath", + intelliSenseMode: "intelliSenseMode", + includePath: "includePath", + defines: "defines", + cStandard: "cStandard", + cppStandard: "cppStandard" +}; + +export interface ViewStateEvent { + isActive: boolean; +} + +export class SettingsPanel { + private configValues: config.Configuration; + private configDirty: boolean = false; + private settingsPanelViewStateChanged = new vscode.EventEmitter(); + private panel: vscode.WebviewPanel; + private disposable: vscode.Disposable = undefined; + private disposablesPanel: vscode.Disposable = undefined; + private static readonly viewType: string = 'settingsPanel'; + private static readonly title: string = 'C/C++ Configurations'; + + constructor() { + this.configValues = { name: undefined }; + this.disposable = vscode.Disposable.from( + this.settingsPanelViewStateChanged, + vscode.window.onDidChangeWindowState(this.onWindowStateChanged, this) + ); + } + + public createOrShow(activeConfiguration: config.Configuration): void { + const column: vscode.ViewColumn = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : undefined; + + // Show existing panel + if (this.panel) { + this.panel.reveal(column, false); + return; + } + + // Create new panel + this.panel = vscode.window.createWebviewPanel( + SettingsPanel.viewType, + SettingsPanel.title, + column || vscode.ViewColumn.One, + { + enableCommandUris: true, + enableScripts: true, + + // Restrict the webview to only loading content from these directories + localResourceRoots: [ + vscode.Uri.file(util.extensionContext.extensionPath), + vscode.Uri.file(path.join(util.extensionContext.extensionPath, 'ui')), + vscode.Uri.file(path.join(util.extensionContext.extensionPath, 'out', 'ui'))] + } + ); + + this.panel.iconPath = vscode.Uri.file(util.getExtensionFilePath("LanguageCCPP_color_128x.png")); + + this.disposablesPanel = vscode.Disposable.from( + this.panel, + this.panel.onDidDispose(this.onPanelDisposed, this), + this.panel.onDidChangeViewState(this.onViewStateChanged, this), + this.panel.webview.onDidReceiveMessage(this.onMessageReceived, this) + ); + + this.panel.webview.html = this.getHtml(); + + this.updateWebview(activeConfiguration); + } + + public get SettingsPanelViewStateChanged(): vscode.Event { + return this.settingsPanelViewStateChanged.event; + } + + public getLastValuesFromConfigUI(): config.Configuration { + return this.configValues; + } + + public updateConfigUI(configuration: config.Configuration): void { + if (this.panel) { + this.updateWebview(configuration); + } + } + + //TODO: validate input paths + // public validatePaths(invalid: boolean) { + // if (this.panel) { + // this.panel.webview.postMessage({ command: 'validateCompilerPath', invalid: invalid }); + // } + // } + + public dispose(): void { + // Clean up resources + this.panel.dispose(); + + if (this.disposable) { + this.disposable.dispose(); + } + + if (this.disposablesPanel) { + this.disposablesPanel.dispose(); + } + } + + private onPanelDisposed(): void { + // Notify listener config panel is not active + if (this.configDirty) { + let viewState: ViewStateEvent = { isActive: false }; + this.settingsPanelViewStateChanged.fire(viewState); + } + + if (this.disposablesPanel) { + this.disposablesPanel.dispose(); + this.panel = undefined; + } + } + + private updateWebview(configuration: config.Configuration): void { + this.configValues = configuration; + // Send a message to the webview to update the values from json. + if (this.panel) { + this.panel.webview.postMessage({ command: 'update', config: configuration }); + this.configDirty = false; + } + } + + private onViewStateChanged(e: vscode.WebviewPanelOnDidChangeViewStateEvent): void { + let viewState: ViewStateEvent = { isActive: e.webviewPanel.active }; + if (this.configDirty || e.webviewPanel.active) { + this.settingsPanelViewStateChanged.fire(viewState); + } + } + + private onWindowStateChanged(e: vscode.WindowState): void { + let viewState: ViewStateEvent = { isActive: e.focused }; + if (this.configDirty || e.focused) { + this.settingsPanelViewStateChanged.fire(viewState); + } + } + + private onMessageReceived(message: any): void { + if (message === null) { + return; + } + switch (message.command) { + case 'change': + this.updateConfig(message); + } + } + + private updateConfig(message: any): void { + let entries: string[]; + this.configDirty = true; + + switch (message.key) { + case elementId.activeConfig: + this.configValues.name = message.value; + break; + case elementId.compilerPath: + this.configValues.compilerPath = message.value; + break; + case elementId.includePath: + entries = message.value.split("\n"); + this.configValues.includePath = entries.filter(e => e); + break; + case elementId.defines: + entries = message.value.split("\n"); + this.configValues.defines = entries.filter(e => e); + break; + case elementId.intelliSenseMode: + this.configValues.intelliSenseMode = message.value; + break; + case elementId.cStandard: + this.configValues.cStandard = message.value; + break; + case elementId.cppStandard: + this.configValues.cppStandard = message.value; + break; + } + } + + private getHtml(): string { + let content: string | undefined; + content = fs.readFileSync(util.getExtensionFilePath("ui/settings.html")).toString(); + + content = content.replace( + /{{root}}/g, + vscode.Uri.file(util.extensionContext.extensionPath) + .with({ scheme: 'vscode-resource' }) + .toString()); + + content = content.replace( + /{{nonce}}/g, + this.getNonce()); + + return content; + } + + private getNonce(): string { + let nonce: string; + const possible: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i: number = 0; i < 32; i++) { + nonce += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return nonce; + } +} diff --git a/Extension/src/commands.ts b/Extension/src/commands.ts index 6ac64c0a2c..2927c07cf0 100644 --- a/Extension/src/commands.ts +++ b/Extension/src/commands.ts @@ -15,7 +15,8 @@ class TemporaryCommandRegistrar { private isActivationReady: boolean = false; private commandsToRegister: string[] = [ - "C_Cpp.ConfigurationEdit", + "C_Cpp.ConfigurationEditJSON", + "C_Cpp.ConfigurationEditUI", "C_Cpp.ConfigurationSelect", "C_Cpp.ConfigurationProviderSelect", "C_Cpp.SwitchHeaderSource", diff --git a/Extension/src/main.ts b/Extension/src/main.ts index 87c1e6122f..c4eed72713 100644 --- a/Extension/src/main.ts +++ b/Extension/src/main.ts @@ -330,7 +330,8 @@ function rewriteManifest(): Promise { "onCommand:extension.pickNativeProcess", "onCommand:extension.pickRemoteNativeProcess", "onCommand:C_Cpp.BuildAndDebugActiveFile", - "onCommand:C_Cpp.ConfigurationEdit", + "onCommand:C_Cpp.ConfigurationEditJSON", + "onCommand:C_Cpp.ConfigurationEditUI", "onCommand:C_Cpp.ConfigurationSelect", "onCommand:C_Cpp.ConfigurationProviderSelect", "onCommand:C_Cpp.SwitchHeaderSource", diff --git a/Extension/tsconfig.json b/Extension/tsconfig.json index d5c5ffdb8e..ff0de3f8eb 100644 --- a/Extension/tsconfig.json +++ b/Extension/tsconfig.json @@ -4,7 +4,7 @@ "target": "es6", "outDir": "out", "lib": [ - "es6" + "es6", "dom" ], "sourceMap": true, "rootDir": ".", diff --git a/Extension/ui/settings.html b/Extension/ui/settings.html new file mode 100644 index 0000000000..1fdfa584f0 --- /dev/null +++ b/Extension/ui/settings.html @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ Microsoft C/C++ Extension + + Microsoft
+ C/C++ Extension +
+
+

Saves to active configuration in c_cpp_properties.json

+
+ Compiler path +

+ + +

+

+

+ IntelliSense mode +

+ +

+
Include path +

+ +
+

+
Defines +

+ +

+
C Standard +

+ +

+
+ C++ Standard +

+ +

+
+ + + + + diff --git a/Extension/ui/settings.ts b/Extension/ui/settings.ts new file mode 100644 index 0000000000..c0e6273518 --- /dev/null +++ b/Extension/ui/settings.ts @@ -0,0 +1,104 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +const elementId: { [key: string]: string } = { + activeConfig: "activeConfig", + compilerPath: "compilerPath", + intelliSenseMode: "intelliSenseMode", + includePath: "includePath", + defines: "defines", + cStandard: "cStandard", + cppStandard: "cppStandard" + // compilerPathInvalid: "compilerPathInvalid", + // includePathInvalid: "includePathInvalid" +}; + +interface VsCodeApi { + postMessage(msg: {}): void; + setState(state: {}): void; + getState(): {}; +} + +declare function acquireVsCodeApi(): VsCodeApi; + +class SettingsApp { + private readonly vsCodeApi: VsCodeApi; + private updating: boolean = false; + + constructor() { + this.vsCodeApi = acquireVsCodeApi(); + + window.addEventListener('message', this.onMessageReceived.bind(this)); + + document.getElementById(elementId.activeConfig).addEventListener("change", this.onChanged.bind(this, elementId.activeConfig)); + + document.getElementById(elementId.compilerPath).addEventListener("change", this.onChanged.bind(this, elementId.compilerPath)); + document.getElementById(elementId.intelliSenseMode).addEventListener("change", this.onChanged.bind(this, elementId.intelliSenseMode)); + + document.getElementById(elementId.includePath).addEventListener("change", this.onChanged.bind(this, elementId.includePath)); + document.getElementById(elementId.defines).addEventListener("change", this.onChanged.bind(this, elementId.defines)); + + document.getElementById(elementId.cStandard).addEventListener("change", this.onChanged.bind(this, elementId.cStandard)); + document.getElementById(elementId.cppStandard).addEventListener("change", this.onChanged.bind(this, elementId.cppStandard)); + + // document.getElementById(ElementId.compilerPathInvalid).style.visibility = "hidden"; + // document.getElementById(ElementId.includePathInvalid).style.visibility = "hidden"; + } + + private onChanged(id: string) { + if (this.updating) { + return; + } + + var x = document.getElementById(id); + this.vsCodeApi.postMessage({ + command: "change", + key: id, + value: x.value + }); + } + + private onMessageReceived(e: MessageEvent) { + const message = e.data; // The json data that the extension sent + switch (message.command) { + case 'update': + this.update(message.config); + break; + //TODO: validate input paths + // case 'validateCompilerPath': + // this.validateInput(ElementId.compilerPathInvalid, message.invalid); + // break; + // case 'validateIncludePath': + // this.validateInput(ElementId.includePathInvalid, message.invalid); + // break; + } + } + + private update(config: any) { + this.updating = true; + try { + document.getElementById(elementId.activeConfig).innerHTML = config.name; + + (document.getElementById(elementId.compilerPath)).value = config.compilerPath; + (document.getElementById(elementId.intelliSenseMode)).value = config.intelliSenseMode; + + document.getElementById(elementId.includePath).innerHTML = (config.includePath.length > 0) ? config.includePath.join("\n") : ""; + document.getElementById(elementId.defines).innerHTML = (config.defines.length > 0 ) ? config.defines.join("\n") : ""; + + (document.getElementById(elementId.cStandard)).value = config.cStandard; + (document.getElementById(elementId.cppStandard)).value = config.cppStandard; + } + finally { + this.updating = false; + } + } + + // private validateInput(elementID: string, invalid: boolean) { + // document.getElementById(elementID).style.visibility = invalid ? "visible" : "hidden"; + // } +} + +new SettingsApp();