diff --git a/vscode-wpilib/eslint.config.js b/vscode-wpilib/eslint.config.js
new file mode 100644
index 00000000..bec74d70
--- /dev/null
+++ b/vscode-wpilib/eslint.config.js
@@ -0,0 +1,5 @@
+// eslint.config.js
+module.exports =
+ {
+ ignores: ["media/", "out/", "resources/"]
+ };
diff --git a/vscode-wpilib/media/main.css b/vscode-wpilib/media/main.css
new file mode 100644
index 00000000..e57c1808
--- /dev/null
+++ b/vscode-wpilib/media/main.css
@@ -0,0 +1,40 @@
+body {
+ background-color: transparent;
+}
+
+.color-list {
+ list-style: none;
+ padding: 0;
+}
+
+.color-entry {
+ width: 100%;
+ display: flex;
+ margin-bottom: 0.4em;
+ border: 1px solid var(--vscode-input-border);
+}
+
+.color-preview {
+ width: 2em;
+ height: 2em;
+}
+
+.color-preview:hover {
+ outline: inset white;
+}
+
+.color-input {
+ display: block;
+ flex: 1;
+ width: 100%;
+ color: var(--vscode-input-foreground);
+ background-color: var(--vscode-input-background);
+ border: none;
+ padding: 0 0.6em;
+}
+
+.add-color-button {
+ display: block;
+ border: none;
+ margin: 0 auto;
+}
diff --git a/vscode-wpilib/media/main.js b/vscode-wpilib/media/main.js
new file mode 100644
index 00000000..26ba7538
--- /dev/null
+++ b/vscode-wpilib/media/main.js
@@ -0,0 +1,178 @@
+//@ts-check
+
+// This script will be run within the webview itself
+// It cannot access the main VS Code APIs directly.
+(function () {
+ const vscode = acquireVsCodeApi();
+ let dropdowns;
+ let buttons;
+ let message;
+ let uninstalls;
+ let installs;
+
+ function populateDropDowns(data) {
+ dropdowns.forEach((dropdown, index) => {
+ const versions = data[index].versionInfo;
+ versions.forEach((versionTuple, i) => {
+ const option = document.createElement('option');
+ option.value = versionTuple.version;
+ option.textContent = versionTuple.version;
+ if (data[index].currentVersion === versionTuple.version) {
+ option.selected = true;
+ buttons[index].textContent = versionTuple.buttonText;
+ if (i === 0) {
+ //This is the first element of the version array thus the most current
+ buttons[index].setAttribute('disabled', 'true');
+ }
+ }
+ dropdown.appendChild(option);
+ });
+ });
+ }
+
+ // Function to generate HTML for installed dependencies
+ function generateInstalledHTML(installed) {
+ const trash = document.getElementById('trashicon')?.innerHTML
+ // Create HTML for installed dependencies
+ let installedHtml = '
Installed VendorDeps ';
+ installed.forEach((dep, index) => {
+ installedHtml += `
+
+
+ ${dep.name} ${dep.currentVersion}
+
+
+
+
+
+
+
+
+
+ `;
+ });
+ return installedHtml;
+ }
+
+ // Function to generate HTML for available dependencies
+ function generateAvailableHTML(available) {
+ // Create HTML for available dependencies
+ let availableHtml = 'Available Dependencies ';
+ available.forEach((dep, index) => {
+ availableHtml += `
+
+
+ ${dep.name} Install
+
+
${dep.version} - ${dep.description}
+
+ `;
+ });
+ return availableHtml;
+ }
+
+ // Add event listeners to the buttons
+ function addEventListeners() {
+ // Handle messages sent from the extension to the webview
+ window.addEventListener('message', event => {
+ message = event.data; // The json data that the extension sent
+ switch (message.type) {
+ case 'updateDependencies':
+ {
+ const installedHTML = generateInstalledHTML(message.installed);
+ const availableHTML = generateAvailableHTML(message.available);
+
+ const installedContainer = document.getElementById('installed-dependencies');
+ const availableContainer = document.getElementById('available-dependencies');
+
+ if (installedContainer) {
+ installedContainer.innerHTML = installedHTML;
+ } else {
+ console.error('Element with ID "installed-dependencies" not found.');
+ }
+
+ if (availableContainer) {
+ availableContainer.innerHTML = availableHTML;
+ } else {
+ console.error('Element with ID "available-dependencies" not found.');
+ }
+
+ dropdowns = document.querySelectorAll('select[id^="version-select-"]');
+ buttons = document.querySelectorAll('button[id^="version-action-"]');
+ uninstalls = document.querySelectorAll('button[id^="uninstall-action-"]');
+ installs = document.querySelectorAll('button[id^="install-action-"]');
+ addDropdownListeners();
+ populateDropDowns(message.installed);
+ break;
+ }
+ }
+ });
+
+ document.getElementById('updateall-action')?.addEventListener('click', () => {
+ vscode.postMessage({ type: 'updateall' })
+ });
+
+ document.getElementById('refresh-action')?.addEventListener('click', () => {
+ vscode.postMessage({ type: 'refresh' })
+ });
+ }
+
+ function addDropdownListeners() {
+ // Add event listener for the dropdown
+ dropdowns.forEach((dropdown, index) =>
+ dropdown.addEventListener('change', () => {
+ const versions = message.installed[index].versionInfo;
+ var selectedText = dropdown.options[dropdown.selectedIndex].text;
+ const version = versions.find(versionTuple => versionTuple.version === selectedText);
+ // Change button text based on selected dropdown value
+ buttons[index].textContent = version.buttonText;
+
+ if (dropdown.selectedIndex === 0 && version.version === message.installed[index].currentVersion) {
+ //This is the first element of the version array thus the most current
+ buttons[index].disabled = true;
+ } else {
+ buttons[index].disabled = false;
+ }
+ })
+ );
+
+ buttons.forEach(button => {
+ button.addEventListener('click', () => {
+ const action = button.getAttribute('id');
+ const index = getStringUntilFirstDashFromRight(action);
+ const drop = document.getElementById("version-select-" + index);
+ if (drop && drop instanceof HTMLSelectElement) {
+ var selectedText = drop.options[drop.selectedIndex].text;
+ // Handle update logic here
+ vscode.postMessage({ type: 'update', version: selectedText, index: index });
+ }
+ });
+ });
+
+ uninstalls.forEach(uninstall => {
+ uninstall.addEventListener('click', () => {
+ const action = uninstall.getAttribute('id');
+ const index = getStringUntilFirstDashFromRight(action);
+ vscode.postMessage({ type: 'uninstall', index: index });
+ });
+ });
+
+ installs.forEach(install => {
+ install.addEventListener('click', () => {
+ const action = install.getAttribute('id');
+ const index = getStringUntilFirstDashFromRight(action);
+ vscode.postMessage({ type: 'install', index: index });
+ });
+ });
+ }
+
+ function getStringUntilFirstDashFromRight(str) {
+ const index = str.lastIndexOf("-");
+ if (index === -1) {
+ return str; // No dash found, return the whole string
+ }
+ return str.substring(index + 1);
+ }
+
+ addEventListeners();
+}());
\ No newline at end of file
diff --git a/vscode-wpilib/media/reset.css b/vscode-wpilib/media/reset.css
new file mode 100644
index 00000000..92d02910
--- /dev/null
+++ b/vscode-wpilib/media/reset.css
@@ -0,0 +1,30 @@
+html {
+ box-sizing: border-box;
+ font-size: 13px;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+ol,
+ul {
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+}
diff --git a/vscode-wpilib/media/trash-can-solid.png b/vscode-wpilib/media/trash-can-solid.png
new file mode 100644
index 00000000..a2126a01
Binary files /dev/null and b/vscode-wpilib/media/trash-can-solid.png differ
diff --git a/vscode-wpilib/media/vscode.css b/vscode-wpilib/media/vscode.css
new file mode 100644
index 00000000..5d12b7e0
--- /dev/null
+++ b/vscode-wpilib/media/vscode.css
@@ -0,0 +1,91 @@
+:root {
+ --container-paddding: 20px;
+ --input-padding-vertical: 6px;
+ --input-padding-horizontal: 4px;
+ --input-margin-vertical: 4px;
+ --input-margin-horizontal: 0;
+}
+
+body {
+ padding: 0 var(--container-paddding);
+ color: var(--vscode-foreground);
+ font-size: var(--vscode-font-size);
+ font-weight: var(--vscode-font-weight);
+ font-family: var(--vscode-font-family);
+ background-color: var(--vscode-editor-background);
+}
+
+ol,
+ul {
+ padding-left: var(--container-paddding);
+}
+
+body > *,
+form > * {
+ margin-block-start: var(--input-margin-vertical);
+ margin-block-end: var(--input-margin-vertical);
+}
+
+*:focus {
+ outline-color: var(--vscode-focusBorder) !important;
+}
+
+a {
+ color: var(--vscode-textLink-foreground);
+}
+
+a:hover,
+a:active {
+ color: var(--vscode-textLink-activeForeground);
+}
+
+code {
+ font-size: var(--vscode-editor-font-size);
+ font-family: var(--vscode-editor-font-family);
+}
+
+button {
+ border: none;
+ padding: var(--input-padding-vertical) var(--input-padding-horizontal);
+ width: 100%;
+ text-align: center;
+ outline: 1px solid transparent;
+ outline-offset: 2px !important;
+ color: var(--vscode-button-foreground);
+ background: var(--vscode-button-background);
+}
+
+button:hover {
+ cursor: pointer;
+ background: var(--vscode-button-hoverBackground);
+}
+
+button:focus {
+ outline-color: var(--vscode-focusBorder);
+}
+
+button.secondary {
+ color: var(--vscode-button-secondaryForeground);
+ background: var(--vscode-button-secondaryBackground);
+}
+
+button.secondary:hover {
+ background: var(--vscode-button-secondaryHoverBackground);
+}
+
+input:not([type='checkbox']),
+textarea {
+ display: block;
+ width: 100%;
+ border: none;
+ font-family: var(--vscode-font-family);
+ padding: var(--input-padding-vertical) var(--input-padding-horizontal);
+ color: var(--vscode-input-foreground);
+ outline-color: var(--vscode-input-border);
+ background-color: var(--vscode-input-background);
+}
+
+input::placeholder,
+textarea::placeholder {
+ color: var(--vscode-input-placeholderForeground);
+}
diff --git a/vscode-wpilib/media/wpilib.png b/vscode-wpilib/media/wpilib.png
new file mode 100644
index 00000000..a1232673
Binary files /dev/null and b/vscode-wpilib/media/wpilib.png differ
diff --git a/vscode-wpilib/package.json b/vscode-wpilib/package.json
index 34bf41da..de2a3674 100644
--- a/vscode-wpilib/package.json
+++ b/vscode-wpilib/package.json
@@ -57,6 +57,15 @@
],
"main": "./out/extension",
"contributes": {
+ "viewsContainers": {
+ "activitybar": [
+ {
+ "id": "wpilib-vendordeps",
+ "title": "WPILib Vendor Dependencies",
+ "icon": "media/wpilib.png"
+ }
+ ]
+ },
"untrustedWorkspaces": {
"supported": "limited",
"description": "%wpilibcore.untrustedWorkspace.title%"
@@ -168,6 +177,17 @@
}
},
"commands": [
+ {
+ "command": "calicoColors.addColor",
+ "category": "Calico Colors",
+ "title": "Add Color"
+ },
+ {
+ "command": "calicoColors.clearColors",
+ "category": "Calico Colors",
+ "title": "Clear Colors",
+ "icon": "$(clear-all)"
+ },
{
"command": "wpilibcore.startRioLog",
"title": "%wpilibcore.startRioLog.title%",
@@ -405,7 +425,15 @@
"name": "C++ Dependencies",
"when": "isWPILibProvidedCpp"
}
- ]
+ ],
+ "wpilib-vendordeps": [
+ {
+ "type": "webview",
+ "id": "wpilib.dependencyView",
+ "name": "WPILib Vendor Dependencies",
+ "icon": "media/wpilib.png"
+ }
+ ]
},
"keybindings": [
{
diff --git a/vscode-wpilib/src/dependencyView.ts b/vscode-wpilib/src/dependencyView.ts
new file mode 100644
index 00000000..29938861
--- /dev/null
+++ b/vscode-wpilib/src/dependencyView.ts
@@ -0,0 +1,435 @@
+import * as vscode from 'vscode';
+import * as fetch from 'node-fetch';
+import { ProjectInfoGatherer, IProjectInfo } from './projectinfo';
+import { VendorLibraries } from './vendorlibraries';
+import { IJsonDependency } from './shared/vendorlibrariesbase';
+import { IExternalAPI } from 'vscode-wpilibapi';
+import { isNewerVersion } from './versions';
+import { logger } from './logger';
+import { localize as i18n } from './locale';
+
+export interface IJsonList {
+ path: string;
+ name: string;
+ version: string;
+ uuid: string;
+ description: string;
+ website: string;
+}
+
+export interface IDepInstalled { name: string; currentVersion: string; versionInfo: { version: string, buttonText: string }[]; }
+
+export interface IJSMessage { type: string; version: string; index: string; }
+
+export class DependencyViewProvider implements vscode.WebviewViewProvider {
+ public static readonly viewType = 'wpilib.dependencyView';
+ private projectInfo: ProjectInfoGatherer;
+ private vendorLibraries: VendorLibraries;
+ private viewInfo: IProjectInfo | undefined;
+ private disposables: vscode.Disposable[] = [];
+ private installedDeps: IJsonDependency[] = [];
+ private availableDeps: IJsonList[] = [];
+ private availableDepsList: IJsonList[] = [];
+ private onlineDeps: IJsonList[] = [];
+ private installedList: IDepInstalled[] = [];
+ private homeDeps: IJsonDependency[] = [];
+ private externalApi: IExternalAPI;
+ private ghURL = `https://raw.githubusercontent.com/wpilibsuite/vendor-json-repo/master/`;
+ private wp: vscode.WorkspaceFolder | undefined;
+
+ private _view?: vscode.WebviewView;
+
+ constructor(
+ private readonly _extensionUri: vscode.Uri,
+ projectInfo: ProjectInfoGatherer,
+ vendorLibraries: VendorLibraries,
+ externalAPI: IExternalAPI
+ ) {
+ this.projectInfo = projectInfo;
+ this.vendorLibraries = vendorLibraries;
+ this.externalApi = externalAPI;
+ }
+
+ public async resolveWebviewView(
+ webviewView: vscode.WebviewView,
+ _context: vscode.WebviewViewResolveContext,
+ _token: vscode.CancellationToken,
+ ) {
+ this._view = webviewView;
+
+ webviewView.webview.options = {
+ // Allow scripts in the webview
+ enableScripts: true,
+
+ localResourceRoots: [
+ this._extensionUri,
+ vscode.Uri.joinPath(this._extensionUri, 'media')
+ ]
+ };
+
+ this.wp = await this.externalApi.getPreferencesAPI().getFirstOrSelectedWorkspace();
+ if (this.wp === undefined) {
+ logger.warn('no workspace');
+ return;
+ }
+
+ if (this.projectInfo) {
+ this.viewInfo = await this.projectInfo.getViewInfo();
+ }
+
+ void this.refresh(this.wp);
+ webviewView.onDidChangeVisibility(() => {
+ if (this.wp) {
+ void this.refresh(this.wp);
+ }
+ });
+
+ this.viewInfo?.vendorLibraries.forEach(item => console.log(item.name.concat(' / ', item.version)));
+
+ webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
+
+ webviewView.webview.onDidReceiveMessage(data => {
+ if (this.isJSMessage(data)) {
+ switch (data.type) {
+ case 'install':
+ {
+ void this.install(data.index);
+ break;
+ }
+ case 'uninstall':
+ {
+ void this.uninstall(data.index);
+ break;
+ }
+ case 'update':
+ {
+ void this.update(data.version, data.index);
+ break;
+ }
+ case 'updateall':
+ {
+ void this.updateall();
+ break;
+ }
+ case 'refresh':
+ {
+ if (this.wp) {
+ void this.refresh(this.wp);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ private isJSMessage(object: unknown): object is IJSMessage {
+ const maybeJSMessage = object as IJSMessage;
+ return maybeJSMessage && typeof maybeJSMessage.type === 'string';
+ }
+
+ private async update(version: string, indexString: string) {
+ const index = parseInt(indexString, 10);
+ // Make sure we have a workspace
+ if (this.wp) {
+ // If the version matches the current version then we want to update to latest
+ const versionToInstall = (version === this.installedList[index].currentVersion)
+ // Get the version of the first element of the array AKA the latest version
+ ? this.installedList[index].versionInfo[0].version
+ // It isn't the current version so user must have specified something else
+ : version;
+
+ // Match both the name and the version
+ const avail = this.availableDeps.find(available =>
+ (versionToInstall === available.version && this.installedList[index].name === available.name));
+ await this.getURLInstallDep(avail);
+ await this.refresh(this.wp);
+ }
+ }
+
+ private async updateall() {
+ if (this.wp) {
+ for (const installed of this.installedList) {
+ if (installed.versionInfo[0].version !== installed.currentVersion && this.wp) {
+ // Match both the name and the version
+ const avail = this.availableDeps.find(available =>
+ (installed.versionInfo[0].version === available.version && installed.name === available.name));
+ await this.getURLInstallDep(avail);
+ }
+ }
+
+ await this.refresh(this.wp);
+ }
+ }
+
+ private async install(index: string) {
+ const avail = this.availableDepsList[parseInt(index, 10)];
+ if (avail && this.wp) {
+ await this.getURLInstallDep(avail);
+ await this.refresh(this.wp);
+ }
+ }
+
+ private async uninstall(index: string) {
+ const uninstall = [this.installedDeps[parseInt(index, 10)]];
+ if (this.wp) {
+ await this.vendorLibraries.uninstallVendorLibraries(uninstall, this.wp);
+ await this.refresh(this.wp);
+ }
+ }
+
+ private async getURLInstallDep(avail: IJsonList | undefined) {
+ if (avail && this.wp) {
+ // Check to see if it is already a URL
+ let url = avail.path;
+ if (url.substring(0, 4) !== 'http') {
+ url = this.ghURL + url;
+ }
+ let dep;
+ try {
+ dep = await this.vendorLibraries.getJsonDepURL(url);
+ } catch {
+ dep = this.homeDeps.find(homdep => homdep.uuid === avail.uuid && homdep.version === avail.version);
+ }
+
+ if (dep) {
+ const success = await this.vendorLibraries.installDependency(dep, this.vendorLibraries.getWpVendorFolder(this.wp), true);
+
+ if (success) {
+ this.vendorLibraries.offerBuild(this.wp);
+ }
+ }
+ }
+ }
+
+ public addDependency() {
+ if (this._view) {
+ this._view.webview.postMessage({ type: 'addDependency' });
+ }
+ }
+
+ public updateDependencies() {
+ if (this._view) {
+ this._view.webview.postMessage({ type: 'updateDependencies', installed: this.installedList, available: this.availableDepsList });
+ }
+ }
+
+ public dispose() {
+ for (const d of this.disposables) {
+ d.dispose();
+ }
+ }
+
+ public async refresh(workspace: vscode.WorkspaceFolder) {
+ this.installedDeps = await this.vendorLibraries.getCurrentlyInstalledLibraries(workspace);
+ this.installedList = [];
+ this.availableDepsList = [];
+
+ this.availableDeps = await this.getAvailableDependencies();
+ if (this.availableDeps.length !== 0) {
+ // Check Github for the VendorDep list
+ if (this.installedDeps.length !== 0) {
+ for (const id of this.installedDeps) {
+ let versionList = [{version: id.version, buttonText: i18n('ui', 'To Latest')}];
+ for (const ad of this.availableDeps) {
+ if (id.uuid === ad.uuid) {
+ // Populate version array with version and button text
+ if (id.version !== ad.version) {
+ if (isNewerVersion(ad.version, id.version)) {
+ versionList.push({version: ad.version, buttonText: i18n('ui', 'Update')});
+ } else {
+ versionList.push({version: ad.version, buttonText: i18n('ui', 'Downgrade')});
+ }
+ }
+ }
+ }
+ // Now we need to sort the version list newest to oldest
+ versionList = this.sortVersions(versionList);
+
+ this.installedList.push({ name: id.name, currentVersion: id.version, versionInfo: versionList });
+ }
+ }
+
+ // We need to group the available deps and filter out the installed ones
+ this.availableDeps.forEach(dep => {
+ // See if the dep is one of the installed deps if so don't add it
+ const installedDep = this.installedDeps.findIndex(depend => depend.uuid === dep.uuid);
+ if (installedDep < 0) {
+ // Check to see if it is already in the available list
+ const foundDep = this.availableDepsList.findIndex(depend => depend.uuid === dep.uuid);
+ if (foundDep < 0) {
+ // Not in the list so just add it
+ this.availableDepsList.push(dep);
+ } else if (isNewerVersion(dep.version, this.availableDepsList[foundDep].version)) {
+ // It was in the list but this version is newer so lets use that
+ this.availableDepsList[foundDep] = dep;
+ }
+ }
+ });
+
+ this.sortInstalled();
+ this.sortAvailable();
+
+ this.updateDependencies();
+ }
+ }
+
+ private sortVersions(versionList: { version: string, buttonText: string }[]): { version: string, buttonText: string }[] {
+ versionList.sort((a, b) => {
+ if (isNewerVersion(a.version, b.version)) {
+ return -1;
+ }
+ else if (a.version === b.version) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+ return versionList;
+ }
+
+ private sortInstalled() {
+ this.installedList.sort((a, b) => {
+ if (a.name.toLowerCase() > b.name.toLowerCase()) {
+ return 1;
+ }
+ else if (a.name.toLowerCase() === b.name.toLowerCase()) {
+ return 0;
+ } else {
+ return -1;
+ }
+ });
+ }
+
+ private sortAvailable() {
+ this.availableDepsList.sort((a, b) => {
+ if (a.name.toLowerCase() > b.name.toLowerCase()) {
+ return 1;
+ }
+ else if (a.name.toLowerCase() === b.name.toLowerCase()) {
+ return 0;
+ } else {
+ return -1;
+ }
+ });
+ }
+
+ public async getAvailableDependencies(): Promise {
+ this.homeDeps = [];
+ const listURL = this.ghURL + `${this.externalApi.getUtilitiesAPI().getFrcYear()}.json`;
+ try {
+ this.onlineDeps = await this.loadFileFromUrl(listURL);
+ } catch (err) {
+ logger.log('Error fetching file', err);
+ this.onlineDeps = [];
+ }
+ this.homeDeps = await this.vendorLibraries.getHomeDirDeps();
+ this.homeDeps.forEach(homedep => {
+ const depList: IJsonList = {
+ path: i18n('ui', homedep.jsonUrl),
+ name: i18n('ui', homedep.name),
+ version: i18n('ui', homedep.version),
+ uuid: i18n('ui', homedep.uuid),
+ description: i18n('ui', 'Loaded from Local Copy'),
+ website: i18n('ui', 'Loaded from Local Copy')
+ };
+ const found = this.onlineDeps.find(onlinedep => onlinedep.uuid === depList.uuid && onlinedep.version === depList.version);
+ if (!found) {
+ this.onlineDeps.push(depList);
+ }
+ });
+
+ return this.onlineDeps;
+ }
+
+ protected async loadFileFromUrl(url: string): Promise {
+ const response = await fetch.default(url, {
+ timeout: 5000,
+ });
+ if (response === undefined) {
+ throw new Error('Failed to fetch file');
+ }
+ if (response.status >= 200 && response.status <= 300) {
+ const text = await response.text();
+ const json = JSON.parse(text) as IJsonList[];
+ if (this.isJsonList(json)) {
+ return json;
+ } else {
+ throw new Error('Incorrect JSON format');
+ }
+ } else {
+ throw new Error('Bad status ' + response.status);
+ }
+ }
+
+ private isJsonList(jsonDepList: IJsonList[]): jsonDepList is IJsonList[] {
+ return jsonDepList.every(jsonDep => {
+ return jsonDep.path !== undefined && jsonDep.name !== undefined
+ && jsonDep.uuid !== undefined && jsonDep.version !== undefined
+ && jsonDep.description !== undefined && jsonDep.website !== undefined; });
+ }
+
+ private _getHtmlForWebview(webview: vscode.Webview): string {
+ // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview.
+ const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'main.js'));
+ const trashUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'trash-can-solid.png'));
+
+ // Return the complete HTML
+ return `
+
+
+
+
+
+ Vendor Dependencies
+
+
+
+
+ Update All Refresh
+
+
+
+
+ ${trashUri}
+
+
+
+ `;
+ }
+}
diff --git a/vscode-wpilib/src/extension.ts b/vscode-wpilib/src/extension.ts
index 5d767dc7..56f45aac 100644
--- a/vscode-wpilib/src/extension.ts
+++ b/vscode-wpilib/src/extension.ts
@@ -35,6 +35,7 @@ import { Gradle2020Import } from './webviews/gradle2020import';
import { Help } from './webviews/help';
import { ProjectCreator } from './webviews/projectcreator';
import { WPILibUpdates } from './wpilibupdates';
+import { DependencyViewProvider } from './dependencyView';
// External API class to implement the IExternalAPI interface
class ExternalAPI implements IExternalAPI {
@@ -158,6 +159,39 @@ async function handleAfterTrusted(externalApi: ExternalAPI, context: vscode.Exte
creationError = true;
}
+ let depProvider: DependencyViewProvider | undefined;
+
+ try {
+ if (projectInfo !== undefined && vendorLibs !== undefined) {
+ depProvider = new DependencyViewProvider(context.extensionUri, projectInfo, vendorLibs, externalApi);
+
+ context.subscriptions.push(
+ vscode.window.registerWebviewViewProvider(DependencyViewProvider.viewType, depProvider));
+
+ if (depProvider !== undefined) {
+ context.subscriptions.push(
+ vscode.commands.registerCommand('wpilib.addDependency', () => {
+ depProvider?.addDependency();
+ }));
+
+/* context.subscriptions.push(
+ vscode.commands.registerCommand('wpilib.removeDependency', () => {
+ depProvider?.removeDependency();
+ })) */
+
+ context.subscriptions.push(
+ vscode.commands.registerCommand('wpilib.updateDependencies', () => {
+ depProvider?.updateDependencies();
+ }));
+ }
+
+ context.subscriptions.push(depProvider);
+ }
+ } catch (err) {
+ logger.error('error creating dependency view', err);
+ creationError = true;
+ }
+
// Create all of our commands that the extension runs
createVsCommands(context, externalApi);
diff --git a/vscode-wpilib/src/projectinfo.ts b/vscode-wpilib/src/projectinfo.ts
index de75b138..e7847ed5 100644
--- a/vscode-wpilib/src/projectinfo.ts
+++ b/vscode-wpilib/src/projectinfo.ts
@@ -108,6 +108,14 @@ Vendor Libraries:
});
}
+ public async getViewInfo(): Promise {
+ const wp = await this.externalApi.getPreferencesAPI().getFirstOrSelectedWorkspace();
+ if (wp === undefined) {
+ return wp;
+ }
+ return this.getProjectInfo(wp);
+ }
+
private async getProjectInfo(workspace: vscode.WorkspaceFolder): Promise {
const vendorLibs = await this.vendorLibraries.getCurrentlyInstalledLibraries(workspace);
diff --git a/vscode-wpilib/src/shared/vendorlibrariesbase.ts b/vscode-wpilib/src/shared/vendorlibrariesbase.ts
index 7f52710a..8ec4aad8 100644
--- a/vscode-wpilib/src/shared/vendorlibrariesbase.ts
+++ b/vscode-wpilib/src/shared/vendorlibrariesbase.ts
@@ -71,7 +71,7 @@ export class VendorLibrariesBase {
return true;
}
- protected getHomeDirDeps(): Promise {
+ public getHomeDirDeps(): Promise {
return this.getDependencies(path.join(this.utilities.getWPILibHomeDir(), 'vendordeps'));
}
diff --git a/vscode-wpilib/src/vendorlibraries.ts b/vscode-wpilib/src/vendorlibraries.ts
index 746677c4..abb00b34 100644
--- a/vscode-wpilib/src/vendorlibraries.ts
+++ b/vscode-wpilib/src/vendorlibraries.ts
@@ -98,8 +98,13 @@ export class VendorLibraries extends VendorLibrariesBase {
return this.getInstalledDependencies(workspace);
}
+ public async getJsonDepURL(url: string): Promise {
+ return this.loadFileFromUrl(url);
+ }
+
private async manageCurrentLibraries(workspace: vscode.WorkspaceFolder): Promise {
const installedDeps = await this.getInstalledDependencies(workspace);
+ const deps: IJsonDependency[] = [];
if (installedDeps.length !== 0) {
const arr = installedDeps.map((jdep) => {
@@ -111,25 +116,41 @@ export class VendorLibraries extends VendorLibrariesBase {
});
if (toRemove !== undefined) {
- const url = this.getWpVendorFolder(workspace);
- const files = await readdirAsync(url);
- for (const file of files) {
- const fullPath = path.join(url, file);
- const result = await this.readFile(fullPath);
- if (result !== undefined) {
- for (const ti of toRemove) {
- if (ti.dep.uuid === result.uuid) {
- await deleteFileAsync(fullPath);
- }
- }
- }
+ for (const ti of toRemove) {
+ deps.push(ti.dep);
}
}
+
+ void this.uninstallVendorLibraries(deps, workspace);
} else {
vscode.window.showInformationMessage(i18n('message', 'No dependencies installed'));
}
}
+ public async uninstallVendorLibraries(toRemove: IJsonDependency[] | undefined, workspace: vscode.WorkspaceFolder) {
+ if (toRemove !== undefined) {
+ let anySucceeded = false;
+ const url = this.getWpVendorFolder(workspace);
+ const files = await readdirAsync(url);
+ for (const file of files) {
+ const fullPath = path.join(url, file);
+ const result = await this.readFile(fullPath);
+ if (result !== undefined) {
+ for (const ti of toRemove) {
+ if (ti.uuid === result.uuid) {
+ await deleteFileAsync(fullPath);
+ anySucceeded = true;
+ }
+ }
+ }
+ }
+
+ if (anySucceeded) {
+ this.offerBuild(workspace);
+ }
+ }
+ }
+
private async offlineUpdates(workspace: vscode.WorkspaceFolder): Promise {
const installedDeps = await this.getInstalledDependencies(workspace);
@@ -164,14 +185,7 @@ export class VendorLibraries extends VendorLibrariesBase {
}
}
if (anySucceeded) {
- const buildRes = await vscode.window.showInformationMessage(i18n('message',
- 'It is recommended to run a "Build" after a vendor update to ensure dependencies are installed correctly. ' +
- 'Would you like to do this now?'), {
- modal: true,
- }, {title: i18n('ui', 'Yes')}, {title: i18n('ui', 'No'), isCloseAffordance: true});
- if (buildRes?.title === i18n('ui', 'Yes')) {
- await this.externalApi.getBuildTestAPI().buildCode(workspace, undefined);
- }
+ this.offerBuild(workspace);
}
}
} else {
@@ -227,14 +241,7 @@ export class VendorLibraries extends VendorLibrariesBase {
}
}
if (anySucceeded) {
- const buildRes = await vscode.window.showInformationMessage(i18n('message',
- 'It is recommended to run a "Build" after a vendor update to ensure dependencies are installed correctly. ' +
- 'Would you like to do this now?'), {
- modal: true,
- }, {title: i18n('ui', 'Yes')}, {title: i18n('ui', 'No'), isCloseAffordance: true});
- if (buildRes?.title === i18n('ui', 'Yes')) {
- await this.externalApi.getBuildTestAPI().buildCode(workspace, undefined);
- }
+ this.offerBuild(workspace);
}
}
} else {
@@ -280,14 +287,7 @@ export class VendorLibraries extends VendorLibrariesBase {
}
}
if (anySucceeded) {
- const buildRes = await vscode.window.showInformationMessage(i18n('message',
- 'It is recommended to run a "Build" after a vendor update to ensure dependencies are installed correctly. ' +
- 'Would you like to do this now?'), {
- modal: true,
- }, {title: i18n('ui', 'Yes')}, {title: i18n('ui', 'No'), isCloseAffordance: true});
- if (buildRes?.title === i18n('ui', 'Yes')) {
- await this.externalApi.getBuildTestAPI().buildCode(workspace, undefined);
- }
+ this.offerBuild(workspace);
}
}
} else {
@@ -316,27 +316,31 @@ export class VendorLibraries extends VendorLibrariesBase {
const success = await this.installDependency(file, this.getWpVendorFolder(workspace), true);
if (success) {
- const buildRes = await vscode.window.showInformationMessage(i18n('message',
- 'It is recommended to run a "Build" after a vendor update to ensure dependencies are installed correctly. ' +
- 'Would you like to do this now?'), {
- modal: true,
- }, {title: i18n('ui', 'Yes')}, {title: i18n('ui', 'No'), isCloseAffordance: true});
- if (buildRes?.title === i18n('ui', 'Yes')) {
- await this.externalApi.getBuildTestAPI().buildCode(workspace, undefined);
- }
+ this.offerBuild(workspace);
} else {
vscode.window.showErrorMessage(i18n('message', 'Failed to install {0}', file.name));
}
}
}
- private getWpVendorFolder(workspace: vscode.WorkspaceFolder): string {
+ public getWpVendorFolder(workspace: vscode.WorkspaceFolder): string {
return this.getVendorFolder(workspace.uri.fsPath);
}
private getInstalledDependencies(workspace: vscode.WorkspaceFolder): Promise {
return this.getDependencies(this.getWpVendorFolder(workspace));
}
+
+ public async offerBuild(workspace: vscode.WorkspaceFolder) {
+ const buildRes = await vscode.window.showInformationMessage(i18n('message',
+ 'It is recommended to run a "Build" after a vendor update. ' +
+ 'Would you like to do this now?'), {
+ modal: false,
+ }, {title: i18n('ui', 'Yes')}, {title: i18n('ui', 'No'), isCloseAffordance: true});
+ if (buildRes?.title === i18n('ui', 'Yes')) {
+ await this.externalApi.getBuildTestAPI().buildCode(workspace, undefined);
+ }
+ }
}
const eventListener: vscode.EventEmitter = new vscode.EventEmitter();