├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── abm ├── abm.html ├── abm.js ├── css │ ├── abmview.css │ ├── editview.css │ ├── infoview.css │ ├── logo.png │ ├── marlin-b.svg │ ├── marlin-w.svg │ └── progress.gif ├── editor.html ├── editor.js ├── format.js ├── img │ ├── abm-tools-32.png │ ├── abm-tools-70.png │ ├── btn-build.svg │ ├── btn-clean.svg │ ├── btn-config.svg │ ├── btn-debug.svg │ ├── btn-monitor.svg │ ├── btn-refresh.svg │ ├── btn-upload.svg │ ├── favicon-dark.svg │ ├── favicon-light.svg │ ├── social-fb.svg │ ├── social-gh.svg │ ├── social-tw.svg │ ├── social-yt.svg │ ├── tool-build.svg │ └── tool-config.svg ├── info.html ├── info.js ├── js │ ├── abmview.js │ ├── editview.js │ ├── grid.js │ ├── infoview.js │ ├── jquery-3.6.0.min.js │ ├── jstepper.js │ ├── marlin.js │ ├── schema.js │ └── vsview.js ├── pane │ ├── geom.html │ ├── lcd.html │ └── sd.html └── prefs.js ├── extension.js ├── icon.png ├── img ├── AB_icon.png ├── AB_menu.png ├── Activity_bar.png ├── B_small.png ├── C_small.png ├── K_small.png ├── T_small.png ├── U_small.png ├── abm-envs.png └── config-editor.png ├── package.json ├── package.nls.json ├── proto.js └── resources ├── AB.svg ├── AB_dark.svg ├── B48x48_dark.svg ├── B48x48_light.svg ├── C48x48_dark.svg ├── C48x48_light.svg ├── K48x48_dark.svg ├── K48x48_light.svg ├── T48x48_dark.svg ├── T48x48_light.svg ├── U48x48_dark.svg └── U48x48_light.svg /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [{*.js,*.css,*.html}] 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | end_of_line = lf 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [thinkyhead] 2 | patreon: thinkyhead 3 | custom: ["http://www.thinkyhead.com/donate-to-marlin"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.vsix 3 | node_modules 4 | .vscode/ 5 | .aider* 6 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | The following enhancements and changes have been made to ***Auto Build Marlin***. 4 | 5 | ## 2.1.74 6 | - Config Editor: Fix int and float value sanitizing 7 | - ABM Panel: Fix parsing of arch list from `pins.h` 8 | 9 | ## 2.1.73 10 | - Config Editor: Fix missing En Driver Type options 11 | - Schema: Fix `HAS_DRIVER` arguments 12 | - Schema: Improve cleanup of expression parentheses 13 | - Ignore Aider files and folders 14 | 15 | ## 2.1.72 16 | - ABM Panel: List all pins files included for `MOTHERBOARD` 17 | - ABM Panel: Provide more space for main content 18 | - Config Editor: Put early items into 'User' group 19 | - Schema: Handle sensor type requirements 20 | - Schema: Define "exclusive" options groups as `ConfigSchema.exclusive[]` 21 | - Move Custom Editor and Info Panel HTML templates to files 22 | - Continuing code refactor for shared schema 23 | 24 | ## 2.1.71 25 | - Fix handling of multi-line value replacement 26 | 27 | ## 2.1.70 28 | - Improve Config Editor layout 29 | - Update Config Editors for external edits 30 | - Fix temp sensor dependent items, add more 31 | 32 | ## 2.1.69 33 | - Fix strict evaluation of empty array 34 | - Fix non-matching SIDs 35 | - Add navigation buttons to show the ABM panel 36 | - Additional code refactoring and cleanup 37 | - Shared schema for Config Editors and Info view 38 | - Preserve basic clone disabled items 39 | - Optimize change messages 40 | - Optimize refresh after edit 41 | 42 | ## 2.1.68 43 | - Fix strict evaluation of empty array 44 | 45 | ## 2.1.67 46 | - Optimize Schema conditionals evaluation 47 | - Use more robust parentheses cleanup method 48 | 49 | ## 2.1.66 50 | - Fix Config Editor changes propagation 51 | - Fix schema evaluation of `ENABLED`/`DISABLED` 52 | - Add schema simple evaluation of `REPEAT` macros 53 | - Add some useful schema iterator parameters 54 | 55 | ## 2.1.65 56 | - Use iterators to simplify config data traversal 57 | - Create serial id index on form creation 58 | 59 | ## 2.1.64 60 | - Fix and improve cleanup for JS evaluation of conditionals 61 | - Clarify that `ConfigSchema` data is grouped by section 62 | - Fix `ConfigSchema.getItems` returning 0 results 63 | - Fix evaluation of mixed-case options (e.g., `DEFAULT_Kp`) 64 | 65 | ## 2.1.63 66 | - Improve Config Editor section pre-ordering and icons 67 | - Better `ConfigSchema` consolidation of define `requires` conditions 68 | - Fix Config Editor handling of underscores in the Filter field 69 | - Show E Sensor IDs in the Auto Build Panel 70 | 71 | ## 2.1.62 72 | - Config Editor: 73 | - Table of Contents navigation 74 | - Iconify Show/Hide filter checkboxes 75 | - Fix save/recall of hidden sections 76 | 77 | ## 2.1.61 78 | - Add commands and buttons to open the Config Editor 79 | - Fix config editor saving 80 | - Update sponsorship URL 81 | 82 | ## 2.1.60 83 | - Config Editor: 84 | - Group sequential items with the same name 85 | - Provide standard options for 'state' and 'dir' types 86 | - More mutual-exclusive checkbox groups 87 | - Preserve collapsed sections on show/hide document 88 | - Clean up and document some code 89 | 90 | ## 2.1.59 91 | - Config Editor: 92 | - Fix handling of `#undef` 93 | - Identify macro defines 94 | - Use checkbox groups for optional distinct items 95 | - Add checkbox groups for like-named items 96 | 97 | ## 2.1.58 98 | - Config Editor: 99 | - Fix filtering options by axis 100 | - Implement radio grouping for LCD items 101 | 102 | ## 2.1.57 103 | - Config Editor: "Show Disabled" checkbox 104 | 105 | ## 2.1.56 106 | - Config Editor: Handle `MB(...)` in schema 107 | 108 | ## 2.1.55 109 | - Update for new `Conditionals-#-abc.h` files 110 | 111 | ## 2.1.54 112 | - Config Editor: Fix parsing of `defined()` in `schema.js` 113 | - Alt/Option changes "Clean" to "Purge" to delete build and libdeps 114 | - Config Editor: Add on-the-fly `log_()` function to `editview.js` 115 | 116 | ## 2.1.53 117 | - Prevent `platformio.ini` from opening by overriding a PlatformIO IDE setting. 118 | Enable "Preserve PIO" in settings to leave PlatformIO IDE settings unchanged. 119 | - Display the version number in the ABM Panel. 120 | - Improve high contrast color schemes. 121 | 122 | ## 2.1.52 123 | - Fix `getItems` handling of `limit` across multiple sections 124 | - Fix the regex that gets the `archs` list from `pins.h` 125 | 126 | ## 2.1.51 127 | - Adjust build command line for different shells (#46) 128 | - Fix init of HIGH / LOW slider custom controls (#68) 129 | - Fix an issue with `//` comments before the first `#define` (#77) 130 | 131 | ## 2.1.50 132 | - Recognize more config sections 133 | - Fix build action button envs 134 | 135 | ## 2.1.49 136 | 137 | - Recognize Chitu firmware with .cbd extension 138 | - Fix schema options set on following settings 139 | - Standardize JSON options cleanup method 140 | - Remove old config parsing borrowed from Configurator 1.0 141 | - Add Formatters information to the README 142 | - Fix formatter debug logging 143 | - Display author and version for a bad config 144 | 145 | ## 2.1.48 146 | 147 | - Fix and extend configuration file parsing 148 | 149 | ## 2.1.47 150 | 151 | - Make the Custom Editor (alpha) hidden by default 152 | - Improve config error handling in Panel View 153 | 154 | ## 2.1.46 155 | 156 | - Custom editor for configuration files (Alpha Preview) 157 | 158 | ## 2.1.45 159 | 160 | - Command to Apply `config.ini` to configurations. 161 | - Command to Export `config.ini`, `schema.json`, `schema.yml` 162 | - Info Panel added (for future use) 163 | 164 | ## 2.1.44 165 | 166 | - Code formatter for pins and general code 167 | - Add categories to settings keys 168 | 169 | ## 2.1.43 170 | 171 | - Show Panel on Startup by default 172 | - Click the Pins file path to open the file 173 | - Auto Reveal Build checkbox, setting 174 | - Light and Dark Marlin SVG for dark/light theme 175 | - Update Sponsor link 176 | - Update jQuery to version 3.6.0 177 | 178 | ## 2.1.42 179 | 180 | - Only init extension for a Marlin folder 181 | - Fix schema @section detection 182 | - Fix build view error 183 | - Run sim/native in fg 184 | 185 | ## 2.1.41 186 | 187 | - Hide 'Run' for incomplete build 188 | 189 | ## 2.1.40 190 | 191 | - Add 'sponsor' field. 192 | 193 | ## 2.1.39 194 | 195 | - Minor code reorganization. 196 | - Fix path escaping for reveal build. 197 | 198 | ## 2.1.38 199 | 200 | - Silent Build setting, with checkbox. 201 | 202 | ## 2.1.37 203 | 204 | - Debuggable postMessage. 205 | - Show STM32F1 chip flash size. 206 | 207 | ## 2.1.36 208 | 209 | - Recognize `.srec` as a complete build. 210 | - Remove `enableProposedApi` from `package.json`. 211 | - Fix debug env detection. 212 | - Add `CHANGELOG.md`. 213 | - Update copyright date. 214 | 215 | ## 2.1.35 216 | 217 | - Fix display of a failed build without a firmware file. 218 | - Enquote the IPC file path, for paths with spaces. 219 | 220 | ## 2.1.34 221 | 222 | - Larger built firmware link with firmware filename. 223 | 224 | ## 2.1.33 225 | 226 | - Fix "Reveal Build" when there are spaces in the path. 227 | - Fix PlatformIO opening `platformio.ini` when "Show on Startup" is enabled. 228 | 229 | ## 2.1.32 230 | 231 | - Display buttons instead of simple links in the sidebar view. 232 | - Fix display of native and simulator build targets. 233 | 234 | ## 2.1.31 235 | 236 | - Keep "Show on Startup" in sync with settings. 237 | 238 | ## 2.1.30 239 | 240 | - Fix the 'Show on Startup' checkbox. 241 | 242 | ## 2.1.29 243 | 244 | - Clarify in error messages that Marlin 2.x is required. 245 | 246 | ## 2.1.28 247 | 248 | - Add a "Show on Startup" option for the impatient. 249 | - Include ***Auto Build Marlin*** commands in the Command Palette. 250 | - Update [YouTube channel URL](https://www.youtube.com/c/MarlinFirmware). 251 | 252 | ## 2.1.27 253 | 254 | - Recognize an `.srec` file as a firmware binary. 255 | 256 | ## 2.1.26 257 | 258 | - Add a 'Monitor' button for quick access to PlatformIO serial monitor. 259 | - Fixed visibility of 'Debug' button during native / simulator build. 260 | - Add helpful parentheses to env type comparisons. 261 | 262 | ## 2.1.25 263 | 264 | - Fix handling of multiple boards in a single `MB(...)` when parsing `pins.h`. 265 | 266 | ## 2.1.24 267 | 268 | - Suppress a warning when the IPC file already exists. 269 | 270 | ## 2.1.23 271 | 272 | - Add the ability to locate the binary in the `.pio/build/{env}/debug` folder 273 | - Add security policy elements to the webView. 274 | 275 | ## 2.1.22 276 | 277 | - Pass `PLATFORMIO_PATH` to the terminal as `PATH` / `Path` so the `platformio` exe is found. 278 | 279 | ## 2.1.21 280 | 281 | - Reveal the latest build if there's more than one. 282 | 283 | ## 2.1.20 284 | 285 | - Find the simulator binary when it's named "`MarlinSimulator`". 286 | 287 | ## 2.1.19 288 | 289 | - Hide the "Run" button during the build. 290 | 291 | ## 2.1.18 292 | 293 | - Remove obsolete command activation events. 294 | - Add "Run" button to start a native target or simulation. 295 | 296 | ## 2.1.17 297 | 298 | - More strict build exists test. 299 | - Support platform-specific native targets. 300 | 301 | ## 2.1.16 302 | 303 | - Improved startup when workspace is not Marlin. 304 | - Minimize SVGs. 305 | - Use gray in SVGs plus CSS blend modes for tool buttons contrast. 306 | 307 | ## 2.1.15 308 | 309 | - Improve reliability of reveal. 310 | 311 | ## 2.1.14 312 | 313 | - Fix startup failsafe. 314 | 315 | ## 2.1.13 316 | 317 | - Hide 'debug' when busy. 318 | 319 | ## 2.1.12 320 | 321 | - Move the model to its own module. 322 | - Add light theme support. 323 | 324 | ## 2.1.11 325 | 326 | - Fix reveal in Windows. 327 | 328 | ## 2.1.10 329 | 330 | - Add `FUNDING.yml`. 331 | - Add link to reveal the build in Explorer / Finder. 332 | - Templated interface. 333 | 334 | ## 2.1.9 335 | 336 | - More robust sensor regex. 337 | 338 | ## 2.1.8 339 | 340 | - Fix reference to missing js. 341 | 342 | ## 2.1.7 343 | 344 | - Look for more `.bin` names. 345 | 346 | ## 2.1.6 347 | 348 | - Basic cleanup, text updates. 349 | 350 | ## 2.1.5 351 | 352 | - Add a Welcome View with messages for different contexts. 353 | - Activate the extension early to keep contexts up to date. 354 | - Only display buttons relevant to environments / build states. 355 | - Load the new pins file when `MOTHERBOARD` changes. 356 | 357 | ## 2.1.4 358 | 359 | - Cleaned up code. 360 | - Updated `README.md`. 361 | 362 | ## 2.1.3 363 | 364 | - Only show Traceback option for "debug" environments. 365 | - Drop `package-lock.json` from the project. 366 | - Show an indeterminate progress bar during the build. 367 | 368 | ## 2.1.2 369 | 370 | - Fix quoted value handling. 371 | 372 | ## 2.1.1 373 | 374 | - Captions with extended descriptions in Build panel. 375 | - Show an error when a build is already underway. 376 | - Add a settings option to re-use the Terminal. 377 | - Fix last build time display. 378 | 379 | ## 2.1.0 380 | 381 | Major upgrade with a new native interface so you see what you need to know before you do your build. The new foundations started in this version will allow us to build a full configuration interface as time goes on and eliminate the need to scan through long text files. 382 | 383 | - Clean up verbose SVG files. 384 | - Drop dependency on `auto_build.py` script and Tkinter. 385 | - Provide an informative WebView display and build interface. 386 | - Only show relevant environments and build options. 387 | 388 | ## 2.0.3 389 | 390 | - Pass process.env to the terminal. 391 | - Apply titlecase to build type titles. 392 | 393 | ## 2.0.2 394 | 395 | - Fix command line for Windows. 396 | - Use node fs to check the script path. 397 | - Use a common function for registered commands. 398 | 399 | ## 2.0.1 400 | 401 | - Handle `auto_build.py` in different locations. 402 | 403 | ## 2.0.0 404 | 405 | Initial release of ***Auto Build Marlin*** to the Marketplace with a version to match the current Marlin release. 406 | 407 | - Add a button to the Activity Bar. 408 | - Add panels with Build, Upload, Debug, and Clean buttons. 409 | - Start to Build, Upload, Debug, or Clean when a button is pressed. 410 | - Provide a helpful README page. 411 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Build Marlin 2 | 3 | "Auto Build Marlin" provides a simplified interface to configure, build, and upload Marlin Firmware. 4 | 5 | The **Auto Build** tool automatically detects the correct environments for your `MOTHERBOARD` and provides an interface to build them. No more editing `platformio.ini` or scanning a long list of environments in the PlatformIO IDE. Just press the **Build** button and go! 6 | 7 | The **Configuration Editor** provides an enhanced interface for editing configurations. This includes a search filter to find the options you need and to discover new and useful features. To use, right-click a file in the VSCode file explorer and select **Open With… > Config Editor**. ***This is an alpha preview and will probably have some issues.*** 8 | 9 | **Custom Commands** for formatting, building, and more. 10 | 11 | ## PlatformIO Required 12 | 13 | When installing "Auto Build Marlin" you'll also be prompted to install the [PlatformIO extension](http://marlinfw.org/docs/basics/install_platformio_vscode.html). PlatformIO handles all the details of the build and is required for "Auto Build Marlin" to function. 14 | 15 | ## Usage 16 | 17 | - Start *Visual Studio Code* and open a project folder with *Marlin Firmware* version 2.0 or later. Be careful to open the folder containing `platformio.ini` and not the "`Marlin`" folder within it. (You may also use the **Import Project…** option from the "PlaformIO Home" page.) 18 | 19 | - The "File Explorer" should point to your Marlin Firmware folder like so: 20 | 21 | ![](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/Activity_bar.png) 22 | 23 | ### Auto Build 24 | 25 | - Click the **Auto Build Marlin** icon ![AutoBuild Icon](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/AB_icon.png) in the Activity Bar (on the far side of the *Visual Studio Code* window) to open the **Auto Build Marlin** sidebar. 26 | 27 | ![ABM Menu](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/AB_menu.png) 28 | 29 | - Use the **Show ABM Panel** button (or click on any of the buttons in the toolbar) to open the Auto Build Marlin panel. If more than one target environment exists for your board you'll have to choose the specific environment to use before the build. 30 | 31 | Icon|Action 32 | ----|------ 33 | ![Build](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/B_small.png)|Start **Marlin Build** to test your Marlin build 34 | ![Upload](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/U_small.png)|Start **Marlin Upload** to install Marlin on your board 35 | ![Traceback](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/T_small.png)|Start **Marlin Upload (traceback)** to install Marlin with debugging 36 | ![Clean](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/C_small.png)|Start **Marlin Clean** to delete old build files 37 | ![Configure](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/K_small.png)|Open the **Configuration Tool** 38 | 39 | - The **Auto Build Marlin** panel displays information about your selected motherboard and basic machine parameters. Each board comes with one or more build environments that are used to generate the final Marlin binary. Choose the environment that best matches your MCU, bootloader, etc. 40 | 41 | ![Environments](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/abm-envs.png) 42 | 43 | ### Configuration Editor 44 | 45 | ![](https://github.com/MarlinFirmware/AutoBuildMarlin/raw/master/img/config-editor.png) 46 | 47 | The Config Editor provides a form divided up into sections with search filtering. In the future we may make the Config Editor the default, but in the meantime there are a few ways to open the Editor. 48 | 49 | - Open Auto Build Marlin and reveal the Welcome Panel. Click on the **Edit Configuration.h** or **Edit Configuration_adv.h** button. 50 | - If `Configuration.h` or `Configuration_adv.h` is open as text, right-click on its tab title and choose "Reopen Editor with…" > Config Editor to switch. 51 | - In the VSCode File Explorer right click on `Configuration.h` or `Configuration_adv.h` and choose "Open with…" to select the **Config Editor**. 52 | 53 | #### Editor Usage 54 | 55 | - Use the navigation sidebar to view a single isolated section, or all. 56 | - Use the "Filter" field to locate options by name. 57 | - Click the "Show/Hide Disabled" button to show or hide disabled options. 58 | - Click the "Show/Hide Comments" button to show or hide comments. 59 | - Click the title of a section to hide/show that section. 60 | - Hold down `alt`/`option` and click on any title to hide/show all sections. 61 | 62 | #### Config Annotations 63 | 64 | - Marlin's standard configuration files are annotated to provide hints to configuration tools. Edit the configuration file text to add your own `@section` markers, provide allowed values for options, or improve documentation. Please submit your [improvements](//github.com/MarlinFirmware/AutoBuildMarlin/pulls) and [suggestions](//github.com/MarlinFirmware/AutoBuildMarlin/issues) to enhance the configuration experience for users worldwide! 65 | 66 | ### Formatters 67 | 68 | - Open the Command Palette and choose "Format Marlin Code." The file will be formatted according to Marlin standards. 69 | - NOTE: The context menu item "Format Document With…" -> "Auto Build Marlin" doesn't work so ignore that menu command for now. 70 | 71 | ## Internals 72 | 73 | The Auto Build Marlin extension for VSCode contributes sidebar panels, a web view, a custom editor, custom formatters, and other commands. This extension is written entirely in Javascript (so we don't have to learn TypeScript). 74 | 75 | ### Bootstrapping 76 | 77 | When VSCode starts the extension it just loads `extension.js`. This file imports `abm.js` and `prefs.js` for utility functions, and `format.js`, `info.js`, and `editor.js` for our feature providers. These files import `js/marlin.js` and `js/schema.js` to process Marlin files, and node `fs` for file functions. Any top level code in these files runs as soon as `extension.js` does. This is when modules init their classes and export their symbols. 78 | 79 | With all that done, `extension.js` defines the code that will register ABM's commands and feature providers with VSCode upon activation. 80 | 81 | ### ConfigSchema 82 | 83 | `ConfigSchema` is the most important class, providing a configuration parser and utility methods for use throughout the extension. Since views scripts also need access this class, `js/schema.js` is made to be loaded either as a module with `requires()` or as a typical HEAD script. 84 | 85 | The first time the extension needs to show a view that uses the schema it reads the configurations. 86 | 87 | ### File Changes 88 | 89 | View providers and views don't share common memory, so data has to be sent between them with serialized messaging. Providers handle messages from the UI in `handleMessageFromUI` and their views receive messages in `handleMessageToUI`. 90 | 91 | Any external file changes cause the whole schema to be refreshed. Changes made in the Custom Editor (i.e., `editview.js`) are applied to a local copy of the schema before being sent in a message to the provider. The provider uses the message to update the shared schema and alert other views to update. 92 | -------------------------------------------------------------------------------- /abm/abm.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | Auto Build Marlin — Home 11 | 12 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 |
62 |
63 |
64 | 65 | 66 |
67 |

Marlin Firmware Auto Build ${abm_version}

68 |
69 |
70 |
71 | 72 | 73 | 74 |
75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
Firmware:
Marlin
Config By:
Machine Name:
Extruders:
Board:
Pins File:
Architectures:
Environments:
85 |
86 | 87 | 88 | 97 | 98 | 99 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
100 |
101 |
102 |
103 |
104 |

Marlin Firmware Configuration Tool

105 |
106 | 107 |
108 |
109 | ${panes.geometry} 110 | ${panes.lcd} 111 | ${panes.sd} 112 |
113 |
114 |
115 | 122 |
123 | 124 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /abm/css/abmview.css: -------------------------------------------------------------------------------- 1 | /* abmview.css */ 2 | /* Styles for Auto Build Marlin Home - and eventually Configurator */ 3 | 4 | * { 5 | --abm-header-height: 50px; 6 | --abm-toolbar-width: 70px; 7 | --abm-social-width: 40px; 8 | --abm-footer-height: 40px; 9 | --abm-font-size-caption: small; 10 | --abm-svg-white: #FFF; 11 | } 12 | 13 | body.vscode-dark, body.vscode-high-contrast { 14 | --marlin-svg: url(marlin-w.svg); 15 | --abm-background: #282C34; 16 | --abm-toolbar-bg: #333636; 17 | --abm-color-border: #CCC; 18 | --abm-tool-bg-hover: #666; 19 | --abm-tool-bg-active: #4D73B9; 20 | --abm-text-color-link: #67DEFF; 21 | --abm-text-color-subtitle: #FFF; 22 | --abm-text-color-vers: #E9DB6A; 23 | --abm-text-color-label: #827461; 24 | --abm-text-color-data: #FFE; 25 | --abm-text-color-pale: #8A8; 26 | --abm-text-color-good: #6E6; 27 | --abm-text-color-limbo: #DB6; 28 | --abm-text-color-busy: #FA0; 29 | --abm-text-color-error: #C44; 30 | --abm-text-color-caption: #58A; 31 | --abm-color-progress-border: #FFF; 32 | } 33 | 34 | body.vscode-light, body.vscode-high-contrast-light { 35 | --marlin-svg: url(marlin-b.svg); 36 | --abm-background: #DEF; 37 | --abm-toolbar-bg: #BCD; 38 | --abm-color-border: #888; 39 | --abm-tool-bg-hover: #666; 40 | --abm-tool-bg-active: #4D73B9; 41 | --abm-text-color-link: #478EFF; 42 | --abm-text-color-subtitle: #276EDF; 43 | --abm-text-color-vers: #1C0C00; 44 | --abm-text-color-label: #3C2200; 45 | --abm-text-color-data: #365; 46 | --abm-text-color-pale: #45F; 47 | --abm-text-color-good: #00D000; 48 | --abm-text-color-limbo: #A80; 49 | --abm-text-color-busy: #A05900; 50 | --abm-text-color-error: #C00; 51 | --abm-text-color-caption: #005B98; 52 | --abm-color-progress-border: #000; 53 | } 54 | 55 | body.vscode-high-contrast { 56 | --abm-background: #000; 57 | --abm-toolbar-bg: #222; 58 | --abm-color-border: #444; 59 | --abm-tool-bg-hover: #666; 60 | --abm-text-color-label: #FFF; 61 | --abm-text-color-vers: #CC0; 62 | --abm-text-color-data: #88F; 63 | --abm-text-color-pale: #DDF; 64 | } 65 | 66 | body.vscode-high-contrast-light { 67 | --abm-background: #FFF; 68 | --abm-color-border: #DDD; 69 | --abm-toolbar-bg: #DDD; 70 | --abm-tool-bg-hover: #AAA; 71 | --abm-text-color-label: #000; 72 | --abm-text-color-vers: #228; 73 | --abm-text-color-data: #222; 74 | --abm-text-color-pale: #228; 75 | --abm-text-color-good: #080; 76 | } 77 | 78 | /* Styles based on theme variant. Where to get a set of hues? */ 79 | 80 | .clear { clear: both; } 81 | 82 | html { margin: 0; padding: 0; } 83 | body { 84 | --abm-progress-offset: 0px; 85 | margin: 0; padding: 0; 86 | background-color: var(--abm-background); 87 | color: var(--abm-text-color-data); 88 | } 89 | 90 | a { text-decoration: none; color: var(--abm-text-color-link); } 91 | a:hover, a:active { color: #579EFF; } 92 | 93 | h1 { 94 | padding: 10px; 95 | margin: 0 0 4px; 96 | background: var(--abm-toolbar-bg); 97 | color: #FFFFF8; 98 | } 99 | 100 | h1 span { font-size: 66%; color: var(--abm-text-color-subtitle); } 101 | h1 span span { color: var(--abm-text-color-vers); } 102 | 103 | h2 { text-align: center; } 104 | 105 | * { 106 | -webkit-touch-callout: none; 107 | -webkit-user-select: none; 108 | -khtml-user-select: none; 109 | -moz-user-select: none; 110 | -ms-user-select: none; 111 | user-select: none; 112 | } 113 | pre { 114 | -webkit-touch-callout: text; 115 | -webkit-user-select: text; 116 | -khtml-user-select: text; 117 | -moz-user-select: text; 118 | -ms-user-select: text; 119 | user-select: text; 120 | } 121 | 122 | .lcall, .rcall { 123 | float: right; 124 | border: 2px solid #666; 125 | padding: 1px; 126 | } 127 | .lcall { 128 | float: left; 129 | margin: 0 0.5em 0.5em 4px; 130 | } 131 | .rcall { 132 | float: right; 133 | margin: 0 4px 0.5em 0.5em; 134 | } 135 | 136 | .col1 { float: left; width: 33%; } 137 | .col12 { float: left; width: 66%; } 138 | .col123 { float: left; width: 100%; } 139 | 140 | /* CSS Grid Layout */ 141 | 142 | #abm-layout { 143 | position: relative; 144 | display: grid; 145 | grid-template-columns: var(--abm-toolbar-width) 1fr var(--abm-social-width); 146 | grid-template-rows: var(--abm-header-height) 1fr var(--abm-footer-height); 147 | grid-column-gap: 0px; 148 | grid-row-gap: 0px; 149 | min-height: 100vh; 150 | background: var(--marlin-svg) transparent no-repeat fixed center; 151 | background-size: 55%; 152 | } 153 | 154 | #abm-toolbar { grid-area: 1 / 1 / 4 / 2; } 155 | .abm-tool { grid-area: 1 / 2 / 3 / 3; display: none; } 156 | #abm-sidebar { grid-area: 1 / 3 / 3 / 4; } 157 | #abm-footer { grid-area: 3 / 2 / 4 / 4; } 158 | 159 | #abm-toolbar { 160 | background: var(--abm-toolbar-bg); 161 | color: #FFFFF8; 162 | text-align: center; 163 | } 164 | 165 | #abm-icon img { padding: 10px 0 5px; } 166 | 167 | .abm-tool>div { margin: 10px; } 168 | 169 | #abm-top { float: right; } 170 | 171 | #abm-sidebar { 172 | background: var(--abm-toolbar-bg); 173 | color: #FFFFF8; 174 | } 175 | #abm-sidebar img { display: inherit; } 176 | #codecat { width: 100%; } 177 | 178 | #abm-footer { 179 | padding-top: 20px; 180 | text-align: center; 181 | background: var(--abm-toolbar-bg); 182 | color: var(--abm-text-color-vers); } 183 | } 184 | #abm-footer span { position: relative; top: -0.6em; } 185 | 186 | table#info { 187 | font-size: 14pt; 188 | margin-bottom: 0.5em; 189 | } 190 | table#info>tbody>tr>th, 191 | table#info>tbody>tr>td { padding: 4px; vertical-align: top; } 192 | table#info>tbody>tr>th { 193 | text-align: right; 194 | white-space: nowrap; 195 | color: var(--abm-text-color-label); 196 | } 197 | 198 | #info div>span { color: var(--abm-text-color-pale); } 199 | 200 | 201 | /* Button Styles */ 202 | 203 | button { cursor: pointer; outline: none; } 204 | button img { vertical-align: text-bottom; } 205 | 206 | #abm-toolbar button, #abm-social a { 207 | display: inline-block; 208 | width: 100%; 209 | background: none; 210 | color: #FFF; 211 | font-size: small; 212 | margin: 0 0 0.5em; 213 | border: none; 214 | opacity: 0.7; 215 | } 216 | #abm-toolbar button img, #abm-social a>img { padding: 0.25em 0; } 217 | #abm-toolbar button span { display: block; padding-bottom: 0.25em; } 218 | #abm-toolbar button:active, 219 | #abm-toolbar button.active:hover, 220 | #abm-toolbar button.active { 221 | background: var(--abm-tool-bg-active); 222 | opacity: 1.0; 223 | } 224 | #abm-toolbar button:hover { background: var(--abm-tool-bg-hover); } 225 | 226 | /* Blend Modes for middle-gray SVG */ 227 | #abm-toolbar button * { color: #808080; mix-blend-mode: color-dodge; } 228 | #abm-toolbar button:hover * { mix-blend-mode: difference; } 229 | #abm-toolbar button:active *, 230 | #abm-toolbar button.active:hover *, 231 | #abm-toolbar button.active * { mix-blend-mode: color-dodge; } 232 | 233 | #info button>img, #abm-top button>img { 234 | width: 1.1em; 235 | height: 1.1em; 236 | margin-top: 2px; 237 | font-size: 90%; 238 | } 239 | 240 | #abm-social { padding-top: var(--abm-header-height); margin-top: 2em; } 241 | #abm-social a { display: block; width: 70%; padding: 0 15%; margin: 0 auto 0.5em; } 242 | #abm-social a>span { display: none; } 243 | #abm-social a:hover { background: var(--abm-tool-bg-hover); opacity: 1; } 244 | 245 | .abm-tool button { 246 | background: rgba(1, 107, 120, 0.5); 247 | color: #FFF; 248 | font-size: 12pt; 249 | padding: 0 6px; 250 | border: 2px solid rgba(1, 107, 120, 0.15); 251 | border-radius: 0.4em; 252 | margin: 0 0.5em 0.25em 0; 253 | } 254 | .abm-tool button:hover { background: rgba(1, 107, 120, 1); } 255 | 256 | pre.config { 257 | opacity: 0.5; 258 | max-height: 60vh; 259 | max-width: 60vw; 260 | padding: 10px; 261 | overflow: auto; 262 | clear: both; 263 | background-color: #FFF; 264 | color: #000; 265 | font-family: "Fira Mono", monospace; 266 | font-size: small; 267 | } 268 | 269 | .abm-caption { 270 | font-size: var(--abm-font-size-caption); 271 | color: var(--abm-text-color-caption); 272 | margin: 0; 273 | } 274 | 275 | #info-pins a { 276 | display: block; 277 | width: 100%; 278 | } 279 | #info-pins a+a { font-size: 66.66%; } 280 | 281 | /* Environment Caption cell */ 282 | .env-more { 283 | display: none; 284 | position: relative; 285 | top: -12px; 286 | } 287 | .env-more span { 288 | height: 0; 289 | margin-left: 1em; 290 | vertical-align: middle; 291 | } 292 | /* Show the caption for exists or busy */ 293 | .exists .env-more, 294 | .busy .env-more { display: table-cell; } 295 | /* Italic caption when busy */ 296 | .busy .env-more { font-style: italic; } 297 | /* The incomplete (when not busy) color */ 298 | .incomplete .env-more { color: var(--abm-text-color-error); } 299 | /* The busy color */ 300 | .busy .env-more, 301 | .incomplete.busy .env-more { color: var(--abm-text-color-busy); } 302 | 303 | /* Environments Sub-Table */ 304 | table#info>tbody>tr>td#info-envs { padding: 0; } /* Envs Cell */ 305 | #info-envs table td+td { padding-left: 1em; } /* Buttons Cell */ 306 | #info-envs table td { padding-bottom: 8px; vertical-align: top; } 307 | 308 | /* Environment Name Colors */ 309 | #info-envs .env-name { color: var(--abm-text-color-limbo); } 310 | #info-envs .exists .env-name { color: var(--abm-text-color-good); } 311 | 312 | #info-envs .busy .env-name, 313 | #info-envs .busy.exists .env-name { color: var(--abm-text-color-busy); } 314 | 315 | #info-envs .exists.incomplete .env-name { color: var(--abm-text-color-error); } 316 | 317 | /* Environment Action Buttons */ 318 | 319 | /* Show CLEAN for existing build */ 320 | #info-envs button.clean { display: none; } 321 | #info-envs .exists button.clean { display: inline-block; } 322 | #info-envs .exists button.clean.opt { display: none; } 323 | 324 | /* More specific selector for alternate function */ 325 | #info-envs button.clean.purge, 326 | #info-envs .exists button.clean.purge { display: none; } 327 | #info-envs .exists button.clean.purge.opt { display: inline-block; } 328 | 329 | /* Only show 'Upload' or 'Debug' depending on the env */ 330 | button.debug { display: none; } 331 | #info-envs .debug button.debug { display: inline-block; } 332 | 333 | /* No upload button for debug or native */ 334 | #info-envs .debug button.upload, 335 | #info-envs .native button.debug, 336 | #info-envs .native button.upload { display: none; } 337 | 338 | /* Only show the "Run" button for a Native target */ 339 | #info-envs button.run, 340 | #info-envs .exists.native.incomplete button.run { display: none; } 341 | #info-envs .exists.native button.run { display: inline-block; } 342 | 343 | /* Hide ALL buttons when busy */ 344 | #info-envs .busy button, 345 | #info-envs .debug.busy button, 346 | #info-envs .busy.exists button, 347 | #info-envs .debug.busy.exists button, 348 | #info-envs .busy.exists button.clean, 349 | #info-envs .busy.exists button.debug, 350 | #info-envs .busy.exists button.run { display: none; } 351 | 352 | /* Show busy indicator when busy */ 353 | #info-envs span.progress { display: none; } 354 | #info-envs .busy span.progress { display: inline-block; } 355 | 356 | /* Hide buttons template and debug text field */ 357 | #env-rows-src, 358 | #debug-text { display: none; } 359 | 360 | #error { 361 | margin: 0; 362 | padding: 0.25em 0; 363 | border-bottom: 2px solid #000; 364 | text-align: center; 365 | font-size: 1.25em; 366 | color: #EF1; 367 | background: #790000; 368 | } 369 | 370 | #error { display: none; } 371 | 372 | .panel-error { 373 | text-align: center; 374 | color: red; 375 | font-size: 150%; 376 | } 377 | 378 | span.progress { 379 | display: inline-block; 380 | width: 128px; 381 | height: 0.5em; 382 | border: 1px solid var(--abm-color-progress-border); 383 | border-radius: 0.25em; 384 | background-color: #FF6; 385 | background-image: url(progress.gif); 386 | background-position-x: var(--abm-progress-offset); 387 | background-repeat: repeat-x; 388 | } 389 | 390 | div.subpanes { margin-top: 0; } 391 | 392 | .subpanes>div { 393 | display: none; 394 | width: 100%; 395 | min-height: 400px; 396 | border: 1px solid var(--abm-color-border); 397 | background: #FFF3; 398 | } 399 | 400 | div.subtabs { margin-bottom: 0; } 401 | div.subtabs button { 402 | border-radius: 0.25em 0.25em 0 0; 403 | margin: 0 2px 0 0; 404 | color: #EEE; 405 | } 406 | div.subtabs button.active { 407 | background: #3BB175; 408 | color: #FFF; 409 | } 410 | 411 | a.reveal { font-size: small; color: var(--abm-text-color-link); } 412 | 413 | form#showy { position: fixed; right: calc(var(--abm-social-width) + 20px); } 414 | form#showy label { display: block; padding: 2px 0; } 415 | form#showy input[type="checkbox"] { position: relative; top: 0.2em; } 416 | -------------------------------------------------------------------------------- /abm/css/editview.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/css/editview.css 4 | * Styles for Configuration Editor 5 | */ 6 | 7 | #hello-button { display: none; } 8 | 9 | * { 10 | --marlin-svg: url(marlin-b.svg); 11 | --abm-edit-color: var(--vscode-editor-color); 12 | --abm-edit-filter-color: var(--vscode-breadcrumb-foreground); 13 | --abm-edit-filter-bg: var(--vscode-breadcrumb-background); 14 | --abm-item-label-width: 24em; 15 | --abm-field-max-width: calc(100% - var(--abm-item-label-width) - 2em); 16 | --nav-width: max(20%, 20em); 17 | } 18 | 19 | body.vscode-light { 20 | --abm-button-bg: #EEE0; 21 | --abm-button-color: #00C8; 22 | --abm-button-hover-bg: #0002; 23 | --abm-button-active-bg: #0004; 24 | --abm-button-active-color: #000; 25 | --abm-edit-bg: #F5F5FDC0; 26 | --abm-edit-bc: #CCCCCCC0; 27 | --abm-edit-color: #000; 28 | --abm-edit-color-comment: #3C2200; 29 | --abm-descript-color: #a90505; 30 | --abm-edit-legend-color: #000; 31 | --abm-edit-legend-bg: #FFFA; 32 | --abm-edit-filter-bg: #000C; 33 | --abm-line-hover-bg: #FFFFFFE0; 34 | } 35 | 36 | body.vscode-dark { 37 | --marlin-svg: url(marlin-w.svg); 38 | --abm-button-bg: #0000; 39 | --abm-button-color: #77F; 40 | --abm-button-hover-bg: #77F2; 41 | --abm-button-active-bg: #FFF2; 42 | --abm-button-active-color: #FFF; 43 | --abm-edit-bg: #00002220; 44 | --abm-edit-bc: #FFFFFF40; 45 | --abm-edit-color: #FFF; 46 | --abm-edit-color-comment: #827461; 47 | --abm-descript-color: #FFC; 48 | --abm-edit-legend-color: #FFF; 49 | --abm-edit-legend-bg: #0008; 50 | --abm-edit-filter-bg: #FFFC; 51 | --abm-line-hover-bg: #00000020; 52 | } 53 | 54 | body.vscode-high-contrast { 55 | --marlin-svg: url(marlin-w.svg); 56 | --abm-line-hover-bg: #00000030; 57 | --abm-edit-bg: #282C34AA; 58 | --abm-edit-bc: #CCCCCCC0; 59 | --abm-edit-color: #FFF; 60 | --abm-edit-color-comment: #827461; 61 | --abm-descript-color: #55ff00; 62 | --abm-edit-legend-color: #FF0; 63 | --abm-edit-legend-bg: #009; 64 | --abm-edit-filter-bg: rgba(37, 128, 185, 0.8); 65 | } 66 | body.vscode-high-contrast-light { 67 | --abm-edit-legend-color: #009; 68 | --abm-edit-legend-bg: #FF0; 69 | } 70 | 71 | .clear { clear: both !important; } 72 | .hide, .nope { display: none !important; } 73 | .show { display: block !important; } 74 | 75 | body { 76 | margin: 0; 77 | padding: 0 4px; 78 | overflow-x: hidden; 79 | } 80 | 81 | #grid { display: none; background-color: #002; } 82 | #grid.grid { display: block; position: absolute; top: 0; left: 0; z-index: 300; } 83 | 84 | a { text-decoration: none; color: #478EFF; } 85 | a:hover, a:active { color: #579EFF; } 86 | 87 | h1 { 88 | padding: 10px; 89 | margin: 0 0 4px; 90 | } 91 | 92 | /* A filter form that doesn't scroll with the page */ 93 | #filter-form { 94 | display: block; 95 | position: fixed; 96 | width: calc(100% - 6px); 97 | left: 2px; 98 | padding: 0.4em 0; 99 | color: var(--abm-edit-filter-color); 100 | background: var(--abm-edit-filter-bg); 101 | opacity: 0.98; 102 | box-shadow: 0 0 38px -11px #000; 103 | border: 1px solid var(--abm-edit-filter-color); 104 | z-index: 100; 105 | text-align: center; 106 | } 107 | 108 | #filter-form input[type = "checkbox"] { margin-left: 2em; } 109 | 110 | /* Emojis in place of checkboxes */ 111 | .iconcb input[type="checkbox"] { display: none; } 112 | .iconcb span { margin-left: 1em; cursor: pointer; opacity: 0.25; } 113 | .iconcb input[type="checkbox"]:checked + span { opacity: 1; } 114 | 115 | #filter { margin-left: 0.25em; padding: 2px 0.5em; border-radius: 1em; } 116 | #filter-count { 117 | float: right; 118 | width: 200px; 119 | padding: 4px 1em; 120 | margin-left: -208px; 121 | text-align: right; 122 | font-size: smaller; 123 | color: var(--abm-edit-filter-color); 124 | } 125 | 126 | /* Comments are unobtrusive but readable */ 127 | div.comment { 128 | width: 0; 129 | margin: 0 0 0.5em calc(var(--abm-item-label-width) + 1em); 130 | } 131 | div.comment.descript { 132 | margin-top: 0.5em; 133 | margin-left: 1em; 134 | /* color: var(--abm-descript-color); */ 135 | } 136 | div.comment.switch { 137 | margin-top: 0.5em; 138 | margin-left: 0.4em; 139 | display: inline-block; 140 | } 141 | div.comment span { 142 | white-space: pre; 143 | font-family: andale mono, monaco, monospace; 144 | font-size: small; 145 | /* color: var(--abm-edit-color-comment); */ 146 | } 147 | .hide-comments div.comment { display: none; } 148 | .hide-disabled div.disabled { display: none; } 149 | 150 | /* Left column for navigation */ 151 | #left-nav-box { 152 | width: var(--nav-width); 153 | position: fixed; 154 | top: 3.25em; /* Velow the filter form */ 155 | height: calc(100vh - 5.25em); 156 | overflow-y: auto; 157 | overflow-x: hidden; 158 | background-color: var(--vscode-editor-background); 159 | z-index: 100; 160 | } 161 | /* Something within to scroll */ 162 | #left-nav { min-height: 50vh; } 163 | /* Buttons will be created in the left column */ 164 | #left-nav button { 165 | display: block; 166 | margin: 0; 167 | width: 100%; 168 | padding: 1px 0.25em; 169 | font-size: 1.2em; 170 | font-weight: bold; 171 | background-color: var(--abm-button-bg); 172 | color: var(--abm-button-color); 173 | border: none; 174 | border-radius: 0.5em 0 0.5em 0.5em; 175 | text-align: left; 176 | z-index: 101; 177 | } 178 | #left-nav.outlined button { 179 | margin: 0 0 -1px; 180 | border: 1px solid threedface; 181 | border-color: #000; 182 | } 183 | #left-nav button.active { 184 | background-color: var(--abm-button-active-bg); 185 | color: var(--abm-button-active-color); 186 | /* border-color: #0000; */ 187 | } 188 | #left-nav button:hover { cursor: pointer; background-color: var(--abm-button-hover-bg); } 189 | #left-nav button.active:hover { background-color: var(--abm-button-active-bg); } 190 | 191 | /* The config form contains all options in a cascade. */ 192 | #config-form { 193 | margin-left: calc(var(--nav-width) + 10px); 194 | width: calc(100vw - (var(--nav-width) + 10px)); 195 | min-width: 40em; 196 | padding-top: 1.25em; /* Pad the top so the contents won't be obscured by the filter form */ 197 | background: var(--marlin-svg) transparent no-repeat fixed calc(var(--nav-width) / 2 + 50%) 50%; 198 | background-size: 55%; 199 | } 200 | /* Leave space on the right for a scroll bar */ 201 | #config-form form { margin-right: 20px; } 202 | #config-form label.opt { 203 | width: var(--abm-item-label-width); 204 | padding: 4px; 205 | display: inline-block; 206 | vertical-align: top; 207 | } 208 | /* Indent levels for conditional settings */ 209 | #config-form label.opt { --abm-label-indent: 0; } 210 | #config-form .d1 label.opt { --abm-label-indent: 2em; } 211 | #config-form .d2 label.opt { --abm-label-indent: 4em; } 212 | #config-form .d3 label.opt { --abm-label-indent: 6em; } 213 | #config-form .d4 label.opt { --abm-label-indent: 8em; } 214 | #config-form .d5 label.opt { --abm-label-indent: 10em; } 215 | #config-form .d6 label.opt { --abm-label-indent: 12em; } 216 | #config-form label.opt { margin-left: var(--abm-label-indent); margin-right: calc(0px - var(--abm-label-indent)); } 217 | 218 | #config-form input[type="text"] { 219 | /* Make the text input fill up the rest of the horizontal space */ 220 | width: var(--abm-field-max-width); 221 | } 222 | #config-form select { max-width: var(--abm-field-max-width); } 223 | #config-form input { display: inline-block; } 224 | 225 | /* Smaller inputs for known types */ 226 | #config-form input[type="text"].char { max-width: 2em; } 227 | #config-form input[type="text"].int, 228 | #config-form input[type="text"].float, 229 | #config-form input[type="text"].pin { max-width: 4em; } 230 | #config-form input[type="text"].enum { max-width: 10em; } 231 | #config-form input[type="text"].int-arr { max-width: 12em; } 232 | #config-form input[type="text"].float-arr { max-width: 18em; } 233 | 234 | .dirty label { font-style: italic; font-weight: bold; } 235 | 236 | span.state { 237 | display: inline-block; 238 | position: relative; 239 | top: -4px; 240 | font-weight: bold; 241 | } 242 | span.state.low { color: red; margin-right: 2px; } 243 | span.state.high { color: green; margin-left: 2px; } 244 | 245 | input[type="checkbox"] { 246 | position: relative; 247 | top: 2px; 248 | } 249 | 250 | .collapsed .section-inner { display: none; } 251 | 252 | #config-form fieldset { 253 | padding-inline-start: 0; 254 | padding-inline-end: 0; 255 | color: var(--abm-edit-color); 256 | background-color: var(--abm-edit-bg); 257 | margin-top: 2em; 258 | border-width: 1px; 259 | border-style: solid; 260 | border-color: var(--abm-edit-bc); 261 | border-radius: 0.5em; 262 | } 263 | #config-form fieldset * { opacity: 1; } 264 | #config-form legend { 265 | font-size: 1.2em; 266 | font-weight: bold; 267 | margin-left: -0.5em; 268 | padding: 0.25em; 269 | border: 2px solid threedface; 270 | border-radius: 0.5em 0 0.5em 0.5em; 271 | color: var(--abm-edit-legend-color); 272 | background-color: var(--abm-edit-legend-bg); 273 | z-index: 101; 274 | } 275 | #config-form legend:hover { cursor: pointer; } 276 | #config-form legend:after { content: " ▽"; } 277 | #config-form .collapsed legend:after { content: " ▷"; } 278 | 279 | #config-form .line { padding: 4px 0; position: relative; } 280 | #config-form .line:hover { background-color: var(--abm-line-hover-bg); } 281 | #config-form .line.disabled { opacity: 0.5; } 282 | 283 | /* Create even/odd stripes for the config form */ 284 | #config-form .line:not(.hide):nth-child(even) { 285 | /* background: #0004; */ 286 | } 287 | 288 | #zero-box { 289 | display: none; 290 | position: absolute; top: 40%; 291 | width: 60%; margin: 0 calc(20% - 1em); padding: 1em; 292 | border: 1px solid; border-radius: 1em; 293 | text-align: center; 294 | font-size: large; 295 | z-index: 200; 296 | } 297 | 298 | /* Prevent text selection on all the elements */ 299 | * { 300 | -webkit-touch-callout: none; 301 | -webkit-user-select: none; 302 | -khtml-user-select: none; 303 | -moz-user-select: none; 304 | -ms-user-select: none; 305 | user-select: none; 306 | } 307 | pre { 308 | -webkit-touch-callout: text; 309 | -webkit-user-select: text; 310 | -khtml-user-select: text; 311 | -moz-user-select: text; 312 | -ms-user-select: text; 313 | user-select: text; 314 | } 315 | 316 | /** 317 | * Custom checkbox that looks like a slider 318 | */ 319 | html { 320 | --slider-width: 35px; 321 | --slider-height: 20px; 322 | --slider-knob-size: 16px; 323 | --slider-knob-offs: calc((var(--slider-height) - var(--slider-knob-size)) / 2); 324 | --slider-knob-tran: calc((var(--slider-knob-size) - var(--slider-knob-offs) + 1px)); 325 | } 326 | label.bool { 327 | margin-top: 4px; 328 | display: inline-block; 329 | height: var(--slider-height); 330 | position: relative; 331 | width: var(--slider-width); 332 | } 333 | #config-form label.bool input { display: none; } 334 | .slider { 335 | position: absolute; 336 | top: 0; right: 0; bottom: 0; left: 0; 337 | background-color: #CCC; 338 | cursor: pointer; 339 | transition: .2s; 340 | } 341 | .slider:before { 342 | position: absolute; 343 | height: var(--slider-knob-size); 344 | width: var(--slider-knob-size); 345 | bottom: var(--slider-knob-offs); 346 | left: var(--slider-knob-offs); 347 | background-color: #FFF; 348 | transition: .2s; 349 | content: ""; 350 | } 351 | input:checked + .slider { background-color: #66BB6A; } 352 | input:checked + .slider:before { transform: translateX(var(--slider-knob-tran)); } 353 | .slider.round { border-radius: calc(var(--slider-height) / 2); } 354 | .slider.round:before { border-radius: 50%; } 355 | 356 | /** 357 | * Media variants for wider and small screens 358 | */ 359 | @media screen and (max-width: 799px) { 360 | #left-nav-box { display: none; } 361 | #config-form { 362 | margin-left: 10px; 363 | width: calc(100vw - 10px); 364 | background: var(--marlin-svg) transparent no-repeat fixed 50% 50%; 365 | background-size: 75%; 366 | } 367 | } 368 | 369 | @media screen and (min-width: 800px) { 370 | * { 371 | --abm-item-label-width: 24em; 372 | } 373 | } 374 | /* When space allows, show sidebar navigation */ 375 | @media screen and (min-width: 1200px) { 376 | * { 377 | --abm-item-label-width: 30em; 378 | } 379 | #left-nav-box { display: block; } 380 | } 381 | -------------------------------------------------------------------------------- /abm/css/infoview.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/css/infoview.css 4 | * Styles for Info sidebar panel 5 | */ 6 | 7 | * { 8 | --marlin-svg: url(marlin-b.svg); 9 | --abm-info-color: var(--vscode-editor-color); 10 | --abm-item-label-width: 24em; 11 | --abm-field-max-width: calc(100% - var(--abm-item-label-width) - 2em); 12 | } 13 | 14 | body.vscode-light { 15 | --abm-svg-color: #00000020; 16 | --abm-info-bg: #F5F5FDC0; 17 | --abm-info-bc: #CCCCCCC0; 18 | --abm-line-hover-bg: #FFFFFFE0; 19 | --abm-info-color: #000; 20 | --abm-info-color-comment: #3C2200; 21 | --abm-info-legend-color: #000; 22 | --abm-info-legend-bg: #FFFA; 23 | } 24 | 25 | body.vscode-dark { 26 | --marlin-svg: url(marlin-w.svg); 27 | --abm-svg-color: #FFFFFF20; 28 | --abm-line-hover-bg: #00000020; 29 | --abm-info-bg: #00002220; 30 | --abm-info-bc: #FFFFFF40; 31 | --abm-info-color: #FFF; 32 | --abm-info-color-comment: #827461; 33 | --abm-info-legend-color: #FFF; 34 | --abm-info-legend-bg: #0008; 35 | } 36 | 37 | body.vscode-high-contrast { 38 | --marlin-svg: url(marlin-w.svg); 39 | --abm-svg-color: #FFFFFF20; 40 | --abm-line-hover-bg: #00000030; 41 | --abm-info-bg: #282C34AA; 42 | --abm-info-bc: #CCCCCCC0; 43 | --abm-info-color: #FFF; 44 | --abm-info-color-comment: #827461; 45 | --abm-info-legend-color: #FF0; 46 | --abm-info-legend-bg: #009; 47 | } 48 | body.vscode-high-contrast-light { 49 | --abm-info-legend-color: #009; 50 | --abm-info-legend-bg: #FF0; 51 | } 52 | 53 | .clear { clear: both !important; } 54 | .hide, .nope { display: none !important; } 55 | .show { display: block !important; } 56 | 57 | body { 58 | margin: 0; 59 | padding: 0; 60 | background: var(--marlin-svg) transparent no-repeat fixed center; 61 | background-size: 80%; 62 | } 63 | h1, h2, h3, h4, h5, h6 { 64 | text-align: center; 65 | } 66 | 67 | /* Prevent text selection on all the elements */ 68 | * { 69 | -webkit-touch-callout: none; 70 | -webkit-user-select: none; 71 | -khtml-user-select: none; 72 | -moz-user-select: none; 73 | -ms-user-select: none; 74 | user-select: none; 75 | } 76 | pre { 77 | -webkit-touch-callout: text; 78 | -webkit-user-select: text; 79 | -khtml-user-select: text; 80 | -moz-user-select: text; 81 | -ms-user-select: text; 82 | user-select: text; 83 | } 84 | 85 | /* Media variants for wider and small screens */ 86 | @media screen and (min-width: 800px) { 87 | } 88 | -------------------------------------------------------------------------------- /abm/css/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/abm/css/logo.png -------------------------------------------------------------------------------- /abm/css/marlin-b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /abm/css/marlin-w.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /abm/css/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/abm/css/progress.gif -------------------------------------------------------------------------------- /abm/editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Configuration Editor 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 |
26 |
27 |
0 Results
28 | 29 | 30 | -------------------------------------------------------------------------------- /abm/editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * abm/editor.js 3 | * 4 | * Provider for the Configuration file editor. 5 | * Loads at extension startup. 6 | * 7 | * This editor is used for `Configuration.h` and `Configuration_adv.h`, which 8 | * are just C header files. The config text is parsed into a dictionary 9 | * which can be used to generate the editor HTML, track changes, etc. 10 | * 11 | * This provider: 12 | * 13 | * - Sets up the initial webview for a config editor. 14 | * - Applies changes sent from a config editor to its text file. 15 | * - Sends messages to a config editor when its file is changed. 16 | * - Goes away when a config file is closed. 17 | * 18 | * TODO: 19 | * - With access to all resources, and loading at startup, it makes sense to 20 | * load all the needed files into a dictionary, process the entire configuration 21 | * here, and send that data (and portions) to the webview instead of the text. 22 | * - Globals and static elements here are persistent for the lifetime of the 23 | * extension and are shared between all editors. So `ConfigEditorProvider.schema` 24 | * can be used to store the entire configuration, plus intermediate conditionals, 25 | * which are needed to correctly handle the second config file. 26 | * - The main challenge is to keep all data synchronized. If we only send the data 27 | * once (and when external changes are made), we don't need to keep our local 28 | * copy of the data in sync. But if we want to send updated data to split views, 29 | * for example, we need to keep our local copy in sync. 30 | * - Before going completely down this local storage road, consider whether it 31 | * saves a lot of processing for the second config file. If the second file's view 32 | * only uses its own local copy, then it only needs to re-process its part of the 33 | * schema for local changes. However, for an external change to Configuration.h, 34 | * it still needs to re-process the entire set of configs. 35 | * - Note that it only needs to process its own part of the config for changes to 36 | * its own file. It would get a different message for changes to the other file. 37 | * 38 | * So here's the plan: 39 | * - For Configuration.h do the normal thing and send the entire config schema to the webview. 40 | * - For Configuration_adv.h load Configuration.h plus Conditionals-2-LCD.h into 41 | * a single text blob, strip out all non-directive lines (disabled directives 42 | * are needed to provide a definitive 'disabled' state for requirements), and 43 | * process that into the schema first, in a section called '_' (underscore), and 44 | * always send that to the view. 45 | * 46 | * Currently: 47 | * - The schema is loaded at startup, and this schema is then used as the canonical 48 | * source for the config data displayed in the editors. 49 | * - Each editor has its own complete copy of its config schema, not just a reference. 50 | * Changes made to the local copy need to be applied separately to the global copy. 51 | handleMessageFromUI() is used to apply changes to the global copy. 52 | * - A combined schema is stored in the global 'schemas' object, with keys 'basic' and 'advanced'. 53 | * - The schema for the file is sent to the webview when the file is opened, 54 | * instead of the text. So we must watch the configs for changes and keep 55 | * them in sync, even when no config editor is open. 56 | * 57 | * List of editing situations that must be considered: 58 | * - Editing Configuration_adv.h with or without Configuration.h open. 59 | * - The simplest case. Global data is updated, but has no effect on previous lines, so 60 | * the Configuration.h 'evaled' fields don't need to be recalculated. 61 | * - Editing Configuration.h without Configuration_adv.h open. 62 | * - Second most simple case. The global data needs to be updated so when Configuration_adv.h 63 | * is opened its conditionals will be up to date. The processing of its conditionals 64 | * can be delayed until the file is opened in the editor, if that processing is very slow. 65 | * - Editing Configuration.h with Configuration_adv.h open. 66 | * - The second config might be open in a visible view, so in that case it will need to be 67 | * synchronized with the global data as edits are made to the first config. But if it is 68 | * hidden, then its local copy updates can wait until it is shown again. 69 | * - Hiding and re-showing. 70 | * - We currently save to a state for the editor view, but while in stasis this state 71 | * cannot be updated as needed (due to changes to Configuration.h or external changes 72 | * to its file). So when the file is re-shown, it may not want to use the saved state. 73 | * We can set a flag so that when the view is shown it will use the global data instead, 74 | * or just always send that global data over. 75 | * - External changes that mess up a file. 76 | * - Hopefully this will be rare, but all we can do is throw an error and reopen it in the 77 | * regular text editor instead. So I need to figure out how to do that. 78 | */ 79 | 'use strict'; 80 | 81 | const vscode = require('vscode'), 82 | fs = require('fs'), 83 | path = require('path'), 84 | abm = require('./abm'), 85 | marlin = require('./js/marlin'), 86 | schema = require('./js/schema'), 87 | vw = vscode.window; 88 | 89 | const ws = vscode.workspace, 90 | wsRoot = (ws && ws.workspaceFolders && ws.workspaceFolders.length) ? ws.workspaceFolders[0].uri.fsPath : ''; 91 | 92 | // The boards list is sent to populate the MOTHERBOARD option. 93 | //const board_list = abm.get_boards_list(); 94 | 95 | // Get the schema for the display and manipulation of configuration info 96 | const ConfigSchema = schema.ConfigSchema; 97 | var schemas; 98 | 99 | // Utility function to get the name of a document from a full path. 100 | const document_name = (document) => document.fileName.split(path.sep).pop(); 101 | 102 | var webviews = []; 103 | 104 | /** 105 | * A provider class that implements resolveCustomTextEditor. 106 | * Loaded once, at extension startup. 107 | */ 108 | class ConfigEditorProvider { 109 | 110 | constructor(context) { this.context = context; } 111 | 112 | // Called by extension.js to register the provider. 113 | static register(context) { 114 | const provider = new ConfigEditorProvider(context); 115 | return vw.registerCustomEditorProvider(ConfigEditorProvider.viewType, provider); 116 | } 117 | 118 | /** 119 | * This provider method is called when a Config Editor is opened. 120 | * The passed document creates a closure, since it is used in subfunctions. 121 | * So there is one instance of this closure for each Config Editor. 122 | */ 123 | async resolveCustomTextEditor(document, panel, _token) { 124 | abm.log("ConfigEditorProvider.resolveCustomTextEditor", document.uri); 125 | 126 | // Set values for items in this closure to use 127 | const doc = document_name(document); 128 | const my = { 129 | filename: doc, 130 | adv: doc == 'Configuration_adv.h', 131 | wv: panel.webview 132 | }; 133 | 134 | // Keep global references to both views 135 | webviews[my.filename] = my.wv; 136 | 137 | function reloadSchemas() { 138 | schemas = schema.combinedSchema(marlin, fs, true); 139 | my.schema = my.adv ? schemas.advanced : schemas.basic; 140 | abm.log("abm/editor.js", schemas); 141 | } 142 | reloadSchemas(); 143 | 144 | // Set up the webview with options and basic html. 145 | my.wv.options = { enableScripts: true }; 146 | my.wv.html = this.getWebViewHtml(my.wv); 147 | 148 | /** 149 | * @brief Tell my webview to rebuild itself with new config data. 150 | * @description Send initial data to this instance's webview so it can build the form. 151 | */ 152 | function initWebview() { 153 | // Get the name of the document. 154 | abm.log(`ConfigEditorProvider.initWebview: ${my.filename}`); 155 | 156 | // Send the pre-parsed data to the web view. 157 | my.wv.postMessage({ type: 'update', bysec: my.schema.bysec }); // editview.js:handleMessageToUI 158 | 159 | // Parse the text and send it to the webview. 160 | //sch.importText(document.getText()); 161 | //my.wv.postMessage({ type: 'update', bysec: sch.bysec }); 162 | 163 | // Originally the webview received the raw text. 164 | //my.wv.postMessage({ type: 'update', text: document.getText() }); 165 | } 166 | 167 | /** 168 | * @brief Update my webview with the latest parsed schema data. 169 | * @description Send updated data to this instance's webview so it can rebuild the form. 170 | */ 171 | function updateWebview(external=false) { 172 | abm.log(`ConfigEditorProvider.updateWebview: ${my.filename}`); 173 | 174 | // Send the parsed data to the basic or advanced config editor view. 175 | if (external) my.wv.postMessage({ type: 'update', bysec: my.schema.bysec }); // editview.js:handleMessageToUI 176 | 177 | // If this isn't the second config, but that file is open, update its view too. 178 | if (!my.adv && 'Configuration_adv.h' in webviews) { 179 | abm.log("updateWebview >> Configuration_adv.h"); 180 | webviews['Configuration_adv.h'].postMessage({ type: 'update', bysec: schemas.advanced.bysec }); 181 | } 182 | } 183 | 184 | /** 185 | * Hook up event handlers to synchronize the webview with the text document. 186 | * 187 | * The text document is the Model, so we sync changes in the document to the 188 | * editor WebView and sync changes in the WebView back to the document. 189 | * 190 | * NOTE: A single text document may be shared between multiple custom editors 191 | * (i.e., in a split custom editor) 192 | */ 193 | 194 | /** 195 | * @brief Listen for changes to the document and update the webview as needed. 196 | * @description Changes may be internal or external. If the changes are internal 197 | * the webview forms will have already been updated. 198 | * 199 | * The event contains a document object with two useful properties: 200 | * changes: An array of vscode.TextDocumentContentChangeEvent[] 201 | * isDirty: true if the document is dirty (not matching disk contents) 202 | */ 203 | var wasDirty = false; 204 | const changeDocumentSubscription = ws.onDidChangeTextDocument(e => { 205 | abm.log("ws.onDidChangeTextDocument", e); 206 | const doc = e.document; 207 | if (doc.uri.fsPath != document.uri.fsPath) return; 208 | 209 | if (wasDirty != doc.isDirty) { 210 | wasDirty = doc.isDirty; 211 | abm.log(`${wasDirty ? "" : "(Saved?) "}Document dirty:${wasDirty} closed:${doc.isClosed}`); 212 | } 213 | 214 | const changes = e.contentChanges; 215 | if (changes.length == 0) return; 216 | 217 | const change1 = changes[0], 218 | is_external = !change1.range.isSingleLine || (change1.range.isSingleLine && change1.range.start.character == change1.range.end.character); 219 | 220 | abm.log( 221 | changes.length + " " + (is_external ? "Ex" : "In") + "ternal change(s) to " + document_name(doc) 222 | + (doc.isClosed ? " (closed)" : "") + (doc.isDirty ? " (dirty)" : ""), 223 | "(range:", change1.range.start.line + ":" + change1.range.start.character, "-", change1.range.end.line + ":" + change1.range.end.character + ")" 224 | ); 225 | 226 | // For non-local changes re-parse the file 227 | if (is_external) reloadSchemas(); 228 | 229 | // Update the web view according to the type of change 230 | updateWebview(is_external); 231 | // TODO: Optimize to only re-parse the changed file, not always both. 232 | }); 233 | 234 | /** 235 | * @brief Update a single document line based on the given define item. 236 | * @description Called in response to 'change' messages sent by the config editor. 237 | * Only modifies 'enabled' state and 'value' so nothing too complicated. 238 | * This adds "edits" to the document text and optionally applies them. 239 | * By using edits they go into the undo/redo stack. 240 | * This also leads to onDidChangeTextDocument events, where we need to 241 | * distinguish between internal and external changes. 242 | */ 243 | function applyConfigChange(document, changes, edit=null) { 244 | abm.log(`ConfigEditorProvider.applyConfigChange: ${my.filename}`, changes); 245 | 246 | // Update the item in our local schema copy. 247 | my.schema.updateEditedItem({ sid:changes.sid, enabled:changes.enabled, value:changes.value }); 248 | 249 | // And for the basic config, update its clone. 250 | if (!my.adv) 251 | schemas.advanced.updateEditedItem({ sid:changes.sid, enabled:changes.enabled, value:changes.value }); 252 | 253 | // Get the line from the document. 254 | const line = changes.line - 1, 255 | text = document.lineAt(line).text; 256 | abm.log(`${line} : ${text}`); 257 | 258 | // Only handle valid #define lines. 259 | const defgrep = /^((\s*)(\/\/)?\s*(#define\s+))([A-Za-z0-9_]+)(\s*)(.*?)(\s*)(\/\/.*)?$/, 260 | match = defgrep.exec(text); 261 | 262 | if (!match) { 263 | console.warn(`[applyConfigChange] Line ${line} is not a #define: ${text}`); 264 | return; 265 | } 266 | 267 | // Init the new text as the existing text of the line 268 | let newtext = text; 269 | 270 | // Update the value of non-switch options 271 | if (changes.type != 'switch') { 272 | newtext = match[1] + match[5] + match[6] + changes.value; 273 | if (match[8]) { 274 | const sp = match[8] ? match[8] : ' ' 275 | newtext += sp + match[9]; 276 | } 277 | } 278 | 279 | // Update un/commenting of #define, as needed 280 | if (changes.enabled) 281 | newtext = newtext.replace(/^(\s*)\/\/+\s*(#define)(\s{1,3})?(\s*)/, '$1$2 $4'); 282 | else 283 | newtext = newtext.replace(/^(\s*)(#define)(\s{1,3})?(\s*)/, '$1//$2 $4'); 284 | 285 | abm.log(`${line} : ${newtext}`); 286 | 287 | // Get the range for the whole line 288 | const range = new vscode.Range(line, 0, line, Number.MAX_VALUE); 289 | 290 | // A single edit can be applied here, or multiple edits can be collected 291 | // and applied all at once if the caller passes their own WorkspaceEdit. 292 | const inplace = edit === null; 293 | if (inplace) edit = new vscode.WorkspaceEdit(); 294 | 295 | // Replace the line with the new text 296 | edit.replace(document.uri, range, newtext); 297 | 298 | // Pad a multi-line value with blank lines to keep line numbers from shifting 299 | let multiline = (my.schema.bysid[changes.sid].line_end ?? changes.line) - changes.line; 300 | if (multiline > 0) { 301 | let clrline = line; 302 | while (multiline--) { 303 | ++clrline; 304 | const range = new vscode.Range(clrline, 0, clrline, Number.MAX_VALUE); 305 | edit.replace(document.uri, range, ""); 306 | } 307 | } 308 | 309 | if (inplace) ws.applyEdit(edit); 310 | } 311 | 312 | // Receive message from the webview via vscode.postMessage. 313 | // The webview sends changes to apply to the underlying document. 314 | function handleMessageFromUI(m) { 315 | abm.log("ConfigEditorProvider.handleMessageFromUI", m); 316 | switch (m.type) { 317 | case 'change': 318 | applyConfigChange(document, m.data); // Update the document text using the given data. 319 | break; 320 | 321 | case 'multi-change': 322 | const edit = new vscode.WorkspaceEdit(); 323 | m.changes.forEach(d => { applyConfigChange(document, d.data, edit); }); 324 | ws.applyEdit(edit); 325 | break; 326 | 327 | case 'hello': 328 | vw.showInformationMessage("Hello from the webview!"); 329 | break; 330 | } 331 | } 332 | my.wv.onDidReceiveMessage(handleMessageFromUI); 333 | 334 | // Get rid of the listener when our editor is closed. 335 | panel.onDidDispose(() => { 336 | changeDocumentSubscription.dispose(); 337 | delete webviews[my.filename]; 338 | }); 339 | 340 | // Send the Configuration schema to the webview to display as a form. 341 | initWebview(); 342 | } 343 | 344 | // Get the URI for a resource in the webview. 345 | resourceUri(webview, dir, file) { 346 | return webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'abm', dir, file)); 347 | } 348 | jsUri(webview, file) { return this.resourceUri(webview, 'js', file); } 349 | 350 | /** 351 | * Static HTML as the starting point for editor webviews. 352 | * Attached scripts are invoked in the webview's context. 353 | */ 354 | getWebViewHtml(webview) { 355 | // Local path to script and css for the webview 356 | const nonce = (0, abm.getNonce)(), // Use a nonce to whitelist which scripts can be run 357 | jqueryUri = this.jsUri(webview, 'jquery-3.6.0.min.js'), 358 | vsviewUri = this.jsUri(webview, 'vsview.js'), 359 | schemaUri = this.jsUri(webview, 'schema.js'), 360 | scriptUri = this.jsUri(webview, 'editview.js'), 361 | gridUri = this.jsUri(webview, 'grid.js'), 362 | cssUri = this.resourceUri(webview, 'css', 'editview.css'); 363 | 364 | return eval(`\`${ abm.load_html('editor.html') }\``); 365 | } 366 | 367 | } 368 | 369 | // Static members 370 | ConfigEditorProvider.viewType = 'abm.configEditor'; 371 | 372 | // Export the provider 373 | exports.ConfigEditorProvider = ConfigEditorProvider; 374 | 375 | abm.log("ConfigEditorProvider (editor.js) loaded"); 376 | -------------------------------------------------------------------------------- /abm/format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/format.js 4 | * Formatting commands for Marlin source code. 5 | */ 6 | 'use strict'; 7 | 8 | const vscode = require("vscode"); 9 | 10 | require('../proto') 11 | 12 | /** 13 | * Apply additional cleanup to code, after Uncrustify formatting with 14 | * buildroot/share/extras/uncrustify.cfg 15 | * TODO: 16 | * - If most of a file is indented due to wrapping #if directives, 17 | * remove that extra level of indentation. 18 | */ 19 | function _codeformat(text) { 20 | // The current code attempts to indent PP directives and code 21 | // that was formatted by something other than Uncrustify: 22 | let result = [], 23 | indent = 0, 24 | lines = text.split('\n'); 25 | const len = lines.length; 26 | for (let i = 0; i < len; i++) { 27 | let line = lines[i]; 28 | // Outdent for #else, #elif, #endif 29 | if (line.match(/^\s*#\s*(else|elif|endif)/) && indent) indent--; 30 | result.push(Array(indent + 1).join(' ') + line); 31 | // Indent following #if, #else, #elif 32 | if (line.match(/^\s*#\s*(if|else|elif)/)) indent++; 33 | } 34 | return result.join('\n'); 35 | } 36 | 37 | /** 38 | * Apply formatting to a pins file, not including indentation. 39 | */ 40 | function _pinsformat(intext) { 41 | const verbose = false; 42 | function log(line, msg) { 43 | if (verbose) console.log(`ABM [${line}] ${msg}`); 44 | } 45 | 46 | const mpatt = [ '-?\\d+', 'P[A-I]\\d+', 'P\\d_\\d+' ], 47 | definePatt = new RegExp(`^\\s*(//)?#define\\s+[A-Z_][A-Z0-9_]+\\s+(${mpatt[0]}|${mpatt[1]}|${mpatt[2]})\\s*(//.*)?$`, 'gm'), 48 | ppad = [ 3, 4, 5 ], 49 | col_comment = 50, 50 | col_value_rj = col_comment - 3; 51 | 52 | var mexpr = []; 53 | for (let m of mpatt) mexpr.push(new RegExp('^' + m + '$')); 54 | 55 | return process_pins_file(intext); 56 | 57 | // Find the pin pattern so non-pin defines can be skipped 58 | function get_pin_pattern(txt) { 59 | let r, m = 0, match_count = [ 0, 0, 0 ]; 60 | definePatt.lastIndex = 0; 61 | while ((r = definePatt.exec(txt)) !== null) { 62 | let ind = -1; 63 | if (mexpr.some((p) => { 64 | ind++; 65 | const didmatch = r[2].match(p); 66 | return r[2].match(p); 67 | }) ) { 68 | const m = ++match_count[ind]; 69 | if (m >= 10) { 70 | return { match: mpatt[ind], pad:ppad[ind] }; 71 | } 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | function process_pins_file(txt) { 78 | if (!txt.length) return '(no text)'; 79 | const patt = get_pin_pattern(txt); 80 | if (!patt) return txt; 81 | const pindefPatt = new RegExp(`^(\\s*(//)?#define)\\s+([A-Z_][A-Z0-9_]+)\\s+(${patt.match})\\s*(//.*)?$`), 82 | noPinPatt = new RegExp(`^(\\s*(//)?#define)\\s+([A-Z_][A-Z0-9_]+)\\s+(-1)\\s*(//.*)?$`), 83 | skipPatt = new RegExp('^(\\s*(//)?#define)\\s+(AT90USB|USBCON|BOARD_.+|.+_MACHINE_NAME|.+_SERIAL|.+_TIMER)\\s+(.+)\\s*(//.*)?$'), 84 | aliasPatt = new RegExp('^(\\s*(//)?#define)\\s+([A-Z_][A-Z0-9_]+)\\s+([A-Z_][A-Z0-9_()]+)\\s*(//.*)?$'), 85 | switchPatt = new RegExp('^(\\s*(//)?#define)\\s+([A-Z_][A-Z0-9_]+)\\s*(//.*)?$'), 86 | undefPatt = new RegExp('^(\\s*(//)?#undef)\\s+([A-Z_][A-Z0-9_]+)\\s*(//.*)?$'), 87 | defPatt = new RegExp('^(\\s*(//)?#define)\\s+([A-Z_][A-Z0-9_]+)\\s+([-_\\w]+)\\s*(//.*)?$'), 88 | condPatt = new RegExp('^(\\s*(//)?#(if|ifn?def|else|elif)(\\s+\\S+)*)\\s+(//.*)$'), 89 | commPatt = new RegExp('^\\s{20,}(//.*)?$'); 90 | const col_value_lj = col_comment - patt.pad - 2; 91 | var r, out = '', check_comment_next = false; 92 | txt.split('\n').forEach(line => { 93 | if (check_comment_next) 94 | check_comment_next = ((r = commPatt.exec(line)) !== null); 95 | 96 | if (check_comment_next) 97 | // Comments in column 45 98 | line = ''.rpad(col_comment) + r[1]; 99 | 100 | else if ((r = pindefPatt.exec(line)) !== null) { 101 | // 102 | // #define MY_PIN [pin] 103 | // 104 | log(line, 'pin'); 105 | const pinnum = r[4].charAt(0) == 'P' ? r[4] : r[4].lpad(patt.pad); 106 | line = r[1] + ' ' + r[3]; 107 | line = line.rpad(col_value_lj) + pinnum; 108 | if (r[5]) line = line.rpad(col_comment) + r[5]; 109 | } 110 | else if ((r = noPinPatt.exec(line)) !== null) { 111 | // 112 | // #define MY_PIN -1 113 | // 114 | log(line, 'pin -1'); 115 | line = r[1] + ' ' + r[3]; 116 | line = line.rpad(col_value_lj) + '-1'; 117 | if (r[5]) line = line.rpad(col_comment) + r[5]; 118 | } 119 | else if ((r = skipPatt.exec(line)) !== null) { 120 | // 121 | // #define SKIP_ME 122 | // 123 | log(line, 'skip'); 124 | } 125 | else if ((r = aliasPatt.exec(line)) !== null) { 126 | // 127 | // #define ALIAS OTHER 128 | // 129 | log(line, 'alias'); 130 | line = r[1] + ' ' + r[3]; 131 | line += r[4].lpad(col_value_rj + 1 - line.length); 132 | if (r[5]) line = line.rpad(col_comment) + r[5]; 133 | } 134 | else if ((r = switchPatt.exec(line)) !== null) { 135 | // 136 | // #define SWITCH 137 | // 138 | log(line, 'switch'); 139 | line = r[1] + ' ' + r[3]; 140 | if (r[4]) line = line.rpad(col_comment) + r[4]; 141 | check_comment_next = true; 142 | } 143 | else if ((r = defPatt.exec(line)) !== null) { 144 | // 145 | // #define ... 146 | // 147 | log(line, 'def'); 148 | line = r[1] + ' ' + r[3] + ' '; 149 | line += r[4].lpad(col_value_rj + 1 - line.length); 150 | if (r[5]) line = line.rpad(col_comment - 1) + ' ' + r[5]; 151 | } 152 | else if ((r = undefPatt.exec(line)) !== null) { 153 | // 154 | // #undef ... 155 | // 156 | log(line, 'undef'); 157 | line = r[1] + ' ' + r[3]; 158 | if (r[4]) line = line.rpad(col_comment) + r[4]; 159 | } 160 | else if ((r = condPatt.exec(line)) !== null) { 161 | // 162 | // #if ... 163 | // 164 | log(line, 'cond'); 165 | line = r[1].rpad(col_comment) + r[5]; 166 | check_comment_next = true; 167 | } 168 | out += line + '\n'; 169 | }); 170 | return out.replace(/\n\n+/g, '\n\n').replace(/\n\n$/g, '\n'); 171 | } 172 | 173 | } 174 | 175 | function format_command(fn, whole=false) { 176 | // Get the active text editor 177 | const editor = vscode.window.activeTextEditor; 178 | if (!editor) return; 179 | const document = editor.document, 180 | selection = editor.selection; 181 | 182 | // Is the document uri a file matching the pattern "pins_*.h"? 183 | const file = document.uri.fsPath; 184 | if (file.match(/^.*\/pins\/.*\/pins_[A-Z0-9_]+\.h$/i)) 185 | fn = _pinsformat; 186 | else 187 | fn = _codeformat; 188 | 189 | //vscode.commands.executeCommand('editor.action.formatDocument').then(() => {}); 190 | 191 | // With no selection, apply to the whole document. 192 | if (selection.isEmpty) whole = true; 193 | const range = whole ? new vscode.Range(0, 0, document.lineCount, 0) : selection; 194 | 195 | // We can use the existing editor 196 | editor.edit(editBuilder => { editBuilder.replace(range, fn(document.getText(range))); }) 197 | .then(success => { console.log(`Edit ${success ? "successful" : "failed"}`); }); 198 | }; 199 | 200 | // Apply a filter to the selection or whole document. 201 | exports.codeformat = () => { format_command(); }; 202 | 203 | /** 204 | * Provider class for a formatter that applies the cascade to C / C++ 205 | * preprocessor directives after formatting has been applied by Uncrustify 206 | * or another formatter. 207 | */ 208 | class PPFormatProvider { 209 | constructor(context) { this.context = context; } 210 | // Called by extension.js to register the provider. 211 | static register(context) { 212 | const provider = new PPFormatProvider(context); 213 | return vscode.languages.registerDocumentFormattingEditProvider(['c','cpp'], provider); 214 | } 215 | // document: vscode.TextDocument, returning: vscode.TextEdit[] 216 | provideDocumentFormattingEdits(document) { 217 | return [vscode.TextEdit.replace(new vscode.Range(0, 0, document.lineCount, 0), _codeformat(document.getText()))]; 218 | } 219 | provideDocumentRangeFormattingEdits(document, range, options, token) { 220 | return [vscode.TextEdit.replace(range, _codeformat(document.getText(range)))]; 221 | } 222 | } 223 | exports.PPFormatProvider = PPFormatProvider; 224 | 225 | //exports.edit_example = (document) => { 226 | // const firstLine = document.lineAt(0); 227 | // if (firstLine.text !== '>>') { 228 | // return [vscode.TextEdit.insert(firstLine.range.start, '>>\n')]; 229 | // } 230 | //}; 231 | 232 | /* 233 | function format(fn, whole=false) { 234 | const editor = vscode.window.activeTextEditor, 235 | document = editor.document; 236 | 237 | vscode.commands.executeCommand('vscode.executeFormatDocumentProvider', { 238 | uri: document.uri, 239 | options: { 240 | tabSize: 2, 241 | insertSpaces: true, 242 | trimAutoWhitespace: true 243 | } 244 | }, 245 | (result) => { 246 | if (!result) return; 247 | const text = result.document.getText(); 248 | if (whole) { 249 | editor.edit((edit) => { 250 | edit.replace(new vscode.Range(0, 0, document.lineCount, 0), fn(text)); 251 | }).then(() => { 252 | editor.selection = new vscode.Selection(0, 0, 0, 0); 253 | }); 254 | return; 255 | } 256 | const selection = editor.selection; 257 | editor.edit((edit) => { 258 | edit.replace(selection, fn(text)); 259 | }).then(() => { 260 | editor.selection = new vscode.Selection(selection.start, selection.start); 261 | }); 262 | }); 263 | } 264 | */ 265 | -------------------------------------------------------------------------------- /abm/img/abm-tools-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/abm/img/abm-tools-32.png -------------------------------------------------------------------------------- /abm/img/abm-tools-70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/abm/img/abm-tools-70.png -------------------------------------------------------------------------------- /abm/img/btn-build.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /abm/img/btn-clean.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /abm/img/btn-config.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /abm/img/btn-debug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /abm/img/btn-monitor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /abm/img/btn-refresh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /abm/img/btn-upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/favicon-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/favicon-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/social-fb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/social-gh.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/social-tw.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/social-yt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /abm/img/tool-build.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /abm/img/tool-config.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /abm/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Marlin Info 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /abm/info.js: -------------------------------------------------------------------------------- 1 | /** 2 | * abm/info.js 3 | * 4 | * Provider for the Marlin Info sidebar. 5 | * Loads at extension startup. 6 | * 7 | * This provider: 8 | * - Sets up the initial webview for the Marlin Info sidebar. 9 | * - Handles commands sent by the Info webview. 10 | * - Sends messages to the Info webview when something changes. 11 | * 12 | * The Info Panel displays the same information as the Build Panel. 13 | * Code developed for this panel may later be applied to all parts 14 | * of ABM for display of configuration info. 15 | */ 16 | 'use strict'; 17 | 18 | const vscode = require("vscode"), 19 | fs = require('fs'), 20 | abm = require('./abm'), 21 | marlin = require('./js/marlin'), 22 | schema = require('./js/schema'), 23 | vw = vscode.window; 24 | 25 | // Get the schema for the display and manipulation of configuration info 26 | const ConfigSchema = schema.ConfigSchema; 27 | var schemas; 28 | 29 | class InfoPanelProvider { 30 | 31 | constructor(context) { this.context = context; } 32 | 33 | // Called by extension.js to register the provider. 34 | static register(context) { 35 | const provider = new InfoPanelProvider(context); 36 | return vw.registerWebviewViewProvider(InfoPanelProvider.viewType, provider); 37 | } 38 | 39 | // Called when the info pane is revealed. 40 | async resolveWebviewView(wvv, wvContext, _token) { 41 | //console.log("InfoPanelProvider.resolveWebviewView"); console.dir(wvv); 42 | 43 | this._view = wvv; // Take ownership of the webview view. 44 | 45 | // Set up the webview with options and basic html. 46 | const wv = wvv.webview; 47 | wv.options = { 48 | enableScripts: true, 49 | localResourceRoots: [ this.context.extensionUri ] 50 | }; 51 | wv.html = this.getWebViewHtml(wv); 52 | 53 | // Handle show/hide events. 54 | wvv.onDidChangeVisibility(() => { 55 | abm.log(`InfoPanelProvider.onDidChangeVisibility: ${wvv.visible}`); 56 | }); 57 | 58 | // Let go of the webview view when it is closed. 59 | wvv.onDidDispose(() => { 60 | abm.log("InfoPanelProvider.onDidDispose:"); 61 | this._view = undefined; 62 | }); 63 | 64 | // Receive message from the webview. 65 | function handleMessageFromUI(m) { 66 | abm.log('InfoPanelProvider::handleMessageFromUI', m); 67 | switch (m.type) { 68 | case 'hello': 69 | vw.showInformationMessage('Hello from the webview!'); 70 | break; 71 | } 72 | } 73 | wv.onDidReceiveMessage(handleMessageFromUI); 74 | 75 | // Tell the webview to display something 76 | // Received by infoview.js:handleMessageToUI 77 | function updateWebview() { 78 | wv.postMessage({ type: 'say', text: "hello" }); // infoview.js:handleMessageToUI 79 | } 80 | 81 | schemas = schema.combinedSchema(marlin, fs); 82 | abm.log("abm/info.js", schemas); 83 | 84 | // Update the view now that the pane has been revealed. 85 | updateWebview(); 86 | } 87 | 88 | // Get the URI for a resource in the webview. 89 | resourceUri(webview, dir, file) { 90 | return webview.asWebviewUri(vscode.Uri.joinPath(this.context.extensionUri, 'abm', dir, file)); 91 | } 92 | jsUri(webview, file) { return this.resourceUri(webview, 'js', file); } 93 | 94 | /** 95 | * Static HTML as the starting point for info webviews. 96 | * Attached scripts are invoked in the webview's context. 97 | */ 98 | getWebViewHtml(webview) { 99 | // Local path to script and css for the webview 100 | const nonce = abm.getNonce(), // Use a nonce to whitelist which scripts can be run 101 | jqueryUri = this.jsUri(webview, 'jquery-3.6.0.min.js'), 102 | vsviewUri = this.jsUri(webview, 'vsview.js'), 103 | schemaUri = this.jsUri(webview, 'schema.js'), 104 | scriptUri = this.jsUri(webview, 'infoview.js'), 105 | cssUri = this.resourceUri(webview, 'css', 'infoview.css'); 106 | 107 | return eval(`\`${ abm.load_html('info.html') }\``); 108 | } 109 | } 110 | 111 | // Static members 112 | InfoPanelProvider.viewType = 'abm.infoView'; 113 | 114 | // Export the provider 115 | exports.InfoPanelProvider = InfoPanelProvider; 116 | 117 | abm.log("InfoPanelProvider (info.js) loaded"); 118 | -------------------------------------------------------------------------------- /abm/js/abmview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * 4 | * abm/js/abmview.js 5 | * 6 | * Manage the WebView UI using messaging with abm.js 7 | * Built on jQuery for easier DOM manipulation. 8 | * 9 | */ 10 | 11 | $(function(){ 12 | 13 | "use strict"; 14 | 15 | // Singleton pattern 16 | var ABM = (() => { 17 | 18 | // private variables and functions 19 | var self, poffset = 0, ptimer = null; 20 | const $err = $('#error'); 21 | 22 | // Return an anonymous object for assignment to ABM 23 | return { 24 | 25 | // public data members 26 | // ... 27 | 28 | // public methods 29 | 30 | init() { 31 | self = this; // a 'this' for use when 'this' is something else 32 | 33 | // 34 | // Hide the Error message when clicked 35 | // 36 | $('body').click(() => { $err.hide(25); }); 37 | 38 | // 39 | // Configurator Buttons show / refresh associated subpanes 40 | // 41 | $('.subtabs button').click((e) => { 42 | abm_pane($(e.target).attr('ref')); 43 | }); 44 | 45 | // 46 | // Checkboxes like "Show on Startup" send a command to update settings 47 | // 48 | $('#showy input').change((e) => { 49 | msg({ command:$(e.target).attr('name'), value:e.target.checked }); 50 | }); 51 | 52 | // 53 | // Watch for option key events and change the title of all button.clean elements 54 | // 55 | $(document).keydown((e) => { 56 | if (e.key === 'Alt') $('button.clean').addClass('opt'); 57 | }); 58 | $(document).keyup((e) => { 59 | if (e.key === 'Alt') $('button.clean').removeClass('opt'); 60 | }); 61 | 62 | // 63 | // Add a handler for webview.postMessage 64 | // 65 | window.addEventListener('message', this.handleMessageToUI); 66 | 67 | // Activate the "Build" tool 68 | msg({ command:'tool', tool:'build' }); // abm.js:handleMessageFromUI 69 | 70 | // Un-hide the first subpane 71 | abm_pane($('.subpanes>div').first().attr('class')); 72 | 73 | }, 74 | 75 | // 76 | // Calls to abm.postMessage or pv.postMessage from abm.html arrive here: 77 | // 78 | handleMessageToUI(event) { 79 | const m = event.data; // JSON sent by the extension 80 | //console.log("ABM View got message:"); console.dir(m); 81 | switch (m.command) { 82 | 83 | case 'tool': abm_tool(m.tool); break; 84 | 85 | case 'pane': abm_pane(m.pane); break; 86 | 87 | case 'define': 88 | // Update a single define element in the UI 89 | break; 90 | 91 | // postValue() 92 | case 'info': 93 | var $dest = $('#info-' + m.tag).text(''); 94 | if (!m.val) 95 | $dest.hide(); 96 | else { 97 | function createItem(text, uri) { 98 | const element = uri 99 | ? $('', { href: '#' }).text(text).on('click', e => { 100 | e.preventDefault(); 101 | _msg({ command: 'openfile', uri }); 102 | }) 103 | : document.createTextNode(text); 104 | return element; 105 | } 106 | 107 | const items = Array.isArray(m.val) 108 | ? m.val.map(v => createItem(v.text, v.uri)) 109 | : [createItem(m.val, m.uri)]; 110 | 111 | items.forEach(item => $dest.append(item)); 112 | $dest.show(); 113 | } 114 | //console.log(`Setting ${m.tag} to ${m.val}`); 115 | break; 116 | 117 | case 'text': 118 | $('#debug-text').show().children('pre').text(m.text); 119 | break; 120 | 121 | case 'noerror': $err.hide(25); break; 122 | 123 | // postError() 124 | case 'error': 125 | $err.removeClass('warning').html(m.error).show(50); 126 | break; 127 | // postWarning() 128 | case 'warning': 129 | $err.addClass('warning').html(m.warning).show(50); 130 | break; 131 | 132 | // Set a checkbox state 133 | case 'check': 134 | $(`#showy input[name="${m.name}"]`).prop('checked', m.state); 135 | break; 136 | 137 | case 'envs': 138 | // Environments for building the current Configuration 139 | // 140 | // m.val = an array of env objects: 141 | // .name - Environment Name 142 | // .debug - Debug Allowed 143 | // .native - Native (Runnable) 144 | // .busy - Show "Please Wait..." State 145 | // .exists - The env build folder exists 146 | // .completed - Build Completed 147 | // .filename - The built binary Filename (if it exists) 148 | // .stamp - Timestamp Message 149 | const $env_td = $('#info-envs').html(''), 150 | $env_rows_src = $('#env-rows-src'), 151 | $envs_table = $(''); 152 | 153 | let has_progress = false; 154 | $.each(m.val, function(i,v) { 155 | // Copy the template
, merging the env name. The is allowed here! 156 | const $env_table_copy = $($env_rows_src.html().replace(//g, v.name)); 157 | let $erows = $env_table_copy.find('tr').addClass(`env-${v.name}`); 158 | 159 | // Set the env name in the new button row 160 | $erows.find('.env-name').text(v.name + (v.note ? ` ${v.note}` : '')); 161 | 162 | // Set env row classes and env caption 163 | let caption = ''; 164 | if (v.debug) $erows.addClass('debug'); 165 | if (v.native) $erows.addClass('native'); 166 | if (v.busy) { 167 | $erows.addClass('busy'); 168 | caption = 'Please Wait…'; 169 | has_progress = true; 170 | } 171 | if (v.exists) { 172 | $erows.addClass('exists'); 173 | if (!v.busy) { 174 | caption = 'Built '; 175 | if ('filename' in v) 176 | caption += `"${v.filename}" `; 177 | caption += v.stamp; 178 | if (!v.completed) { 179 | $erows.addClass('incomplete'); 180 | caption += ' (incomplete)'; 181 | } 182 | else 183 | caption = `📁  ${caption}`; 184 | } 185 | } 186 | $erows.find('.env-more span').html(caption); 187 | 188 | $envs_table.append($erows); 189 | }); 190 | 191 | if (ptimer) { clearInterval(ptimer); ptimer = null; } 192 | if (has_progress) { 193 | ptimer = setInterval(() => { 194 | poffset = (poffset + 30) % 32; 195 | $('body').get(0).style.setProperty('--abm-progress-offset', poffset + 'px'); 196 | }, 50); 197 | } 198 | 199 | $env_td.append($envs_table); 200 | break; 201 | } 202 | }, 203 | 204 | EOF: null 205 | }; 206 | 207 | })(); 208 | 209 | ABM.init(); 210 | msg({ command:'ui-ready' }); // abm.js:handleMessageFromUI 211 | 212 | }); 213 | -------------------------------------------------------------------------------- /abm/js/grid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * "Enter The Grid" 3 | * 4 | * Screensaver shell from: 5 | * - https://github.com/tlrobinson/WebSaver 6 | * 7 | * Javascript from CodePen sketch: 8 | * - https://codepen.io/P3R0/pen/MwgoKv 9 | */ 10 | 'use strict'; 11 | 12 | var $grid, gridTimer; 13 | 14 | function startGrid() { 15 | stopGrid(); 16 | 17 | const $w = $(window); 18 | var wtimer = null; 19 | $w.on('resize', () => { 20 | if (wtimer) clearTimeout(wtimer); 21 | wtimer = setTimeout(startGrid, 200); 22 | }); 23 | 24 | $grid = $('#grid'); 25 | if (!$grid.length) $grid = $('').prependTo('body'); 26 | 27 | // Watch the window size and resize the grid accordingly 28 | //$w.resize(() => { 29 | // $grid.attr({ width:$w.width(), height:$w.height() }); 30 | //}).resize(); 31 | 32 | // Configure the appearance 33 | const drop_color = "#0F0", 34 | font_size = 24, 35 | col_gap = 0; 36 | 37 | const charset = "ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン1234567890".split(''); 38 | 39 | var w = window.innerWidth, h = window.innerHeight; 40 | //if (w < h) w = h; 41 | $grid.prop({ width: w, height: h }) 42 | .css({ marginRight: -w, marginBottom: -h }); 43 | 44 | const ctx = $grid[0].getContext("2d"); 45 | ctx.font = `${font_size}px arial`; 46 | 47 | // Column size and number of columns 48 | const col_size = font_size + col_gap, columns = w / col_size, rows = h / font_size; 49 | 50 | function rndint(n) { return Math.floor(Math.random() * n); } 51 | 52 | function newdrop() { 53 | const intvl = rndint(4); 54 | return { y:-rndint(rows), int:intvl, cnt:intvl }; 55 | } 56 | 57 | // Init all drops at the top of the screen 58 | var drops = []; 59 | for (var x = 0; x < columns; x++) drops.push(newdrop()); 60 | 61 | // Draw the characters 62 | function draw() { 63 | // Black BG for the canvas 64 | // translucent BG to show trail 65 | ctx.fillStyle = "rgba(0, 0.01, 0, 0.05)"; 66 | ctx.fillRect(0, 0, w, h); 67 | 68 | ctx.fillStyle = drop_color; 69 | 70 | const len = drops.length; 71 | for (var i = 0; i < len; i++) { 72 | if (drops[i].cnt--) continue; 73 | 74 | drops[i].cnt = drops[i].int; 75 | drops[i].y++; 76 | 77 | // Send the drop back to the top randomly after it has crossed the screen 78 | // Add a randomness to the reset to make the drops scattered on the Y axis 79 | var y = drops[i].y * font_size; 80 | if (y > h) drops[i] = newdrop(); 81 | 82 | // Print a random character 83 | ctx.fillText(charset[rndint(charset.length)], i * col_size, y); 84 | } 85 | } 86 | 87 | $grid.addClass('grid').on('click', stopGrid); 88 | gridTimer = setInterval(draw, 33); 89 | } 90 | 91 | function stopGrid() { 92 | // Remove the resize handler from the window 93 | $(window).off('resize', startGrid); 94 | 95 | // Stop the animation timer 96 | if (gridTimer) { 97 | clearInterval(gridTimer); 98 | gridTimer = null; 99 | } 100 | 101 | // Remove the grid from the DOM 102 | if ($grid) $grid.remove(); 103 | $grid = null; 104 | } 105 | -------------------------------------------------------------------------------- /abm/js/infoview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/js/infoview.js 4 | * 5 | * This script runs when the view is created / revealed. 6 | * 7 | * The webview already contains the base HTML, so we: 8 | * - Set up the initial info webview. 9 | */ 10 | 'use strict'; 11 | 12 | $(function () { 13 | 14 | var verbose = false; 15 | function log(message, data) { 16 | if (!verbose) return; 17 | console.log(`[editview] ${message}`); 18 | if (data !== undefined) console.dir(data); 19 | } 20 | 21 | // References to elements in the webview. 22 | const $element = $('#element'); 23 | 24 | // Set up the view anew. 25 | function initInfoView() { 26 | // Set up the webview with options and basic html. 27 | } 28 | 29 | // Save the current view state. 30 | // Use this to restore the view when it is revealed. 31 | function saveWebViewState() { 32 | const data = { data: {} }; 33 | log('saveWebViewState', data); 34 | vscode.setState(data); 35 | } 36 | 37 | /** 38 | * @brief Handle messages sent from the provider with iview.postMessage(msg) 39 | * @description Handle 'message' events sent directly to the view. 40 | * @param {object} message - The message object. 41 | */ 42 | function handleMessageToUI(m) { 43 | log("infoview.js : handleMessageToUI", m); 44 | switch (m.type) { 45 | // Update the whole form in response to an external change. 46 | case 'info': 47 | //drawInfo(m.data); 48 | break; 49 | 50 | // Display an error message 51 | case 'error': 52 | $('#error').text(m.text).show().click(() => { $('#error').hide(); }); 53 | break; 54 | } 55 | } 56 | window.addEventListener('message', (e) => { handleMessageToUI(e.data); }); 57 | 58 | // 59 | // View was revealed. 60 | // 61 | // Webviews are normally torn down when not visible and 62 | // re-created when they become visible again. 63 | // 64 | 65 | // Create elements, add handlers, fill in initial info, etc. 66 | initInfoView(); 67 | 68 | // 69 | // Info Panel Revealed 70 | // 71 | // If there is state data then we can skip the parser and build the form. 72 | const state = vscode.getState(); 73 | if (state) { 74 | log("Got VSCode state", state); 75 | if ('data' in state) { 76 | log("Init Marlin Info Webview with stored data") 77 | initInfoView(); 78 | } 79 | } 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /abm/js/jstepper.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery "stepper" Plugin 3 | * version 0.0.1 4 | * @requires jQuery v1.3.2 or later 5 | * @requires jCanvas 6 | * 7 | * Authored 2011-06-11 Scott Lahteine (thinkyhead.com) 8 | * 9 | * A very simple numerical stepper. 10 | * TODO: place arrows based on div size, make 50/50 width 11 | * 12 | * Usage example: 13 | * 14 | * $('#mydiv').jstepper({ 15 | * min: 1, 16 | * max: 4, 17 | * val: 1, 18 | * arrowWidth: 15, 19 | * arrowHeight: '22px', 20 | * color: '#FFF', 21 | * acolor: '#F70', 22 | * hcolor: '#FF0', 23 | * id: 'select-me', 24 | * stepperClass: 'inner', 25 | * textStyle: {width:'1.5em',fontSize:'20px',textAlign:'center'}, 26 | * onChange: function(v) { }, 27 | * }); 28 | * 29 | */ 30 | ;(function($) { 31 | var un = 'undefined'; 32 | 33 | $.jstepperArrows = [ 34 | { name:'prev', poly:[[1.0,0],[0,0.5],[1.0,1.0]] }, 35 | { name:'next', poly:[[0,0],[1.0,0.5],[0,1.0]] } 36 | ]; 37 | 38 | $.fn.jstepper = function(args) { 39 | 40 | return this.each(function() { 41 | 42 | var defaults = { 43 | min: 1, 44 | max: null, 45 | val: null, 46 | active: true, 47 | placeholder: null, 48 | arrowWidth: 0, 49 | arrowHeight: 0, 50 | color: '#FFF', 51 | hcolor: '#FF0', 52 | acolor: '#F80', 53 | id: '', 54 | stepperClass: '', 55 | textStyle: '', 56 | onChange: (function(v){ if (typeof console.log !== 'undefined') console.log("val="+v); }) 57 | }; 58 | 59 | args = $.extend(defaults, args || {}); 60 | 61 | var min = args.min * 1, 62 | max = (args.max !== null) ? args.max * 1 : min, 63 | span = max - min + 1, 64 | val = (args.val !== null) ? args.val * 1 : min, 65 | active = !args.disabled, 66 | placeholder = args.placeholder, 67 | arrowWidth = 1 * args.arrowWidth.toString().replace(/px/,''), 68 | arrowHeight = 1 * args.arrowHeight.toString().replace(/px/,''), 69 | color = args.color, 70 | hcolor = args.hcolor, 71 | acolor = args.acolor, 72 | $prev = $(''), 73 | $marq = $('
').css({float:'left',textAlign:'center'}), 74 | $next = $(''), 75 | arrow = [ $prev.find('canvas')[0], $next.find('canvas')[0] ], 76 | $stepper = $('').append($prev).append($marq).append($next).append('
'), 77 | onChange = args.onChange; 78 | 79 | if (args.id) $stepper[0].id = args.id; 80 | if (args.stepperClass) $stepper.addClass(args.stepperClass); 81 | if (args.textStyle) $marq.css(args.textStyle); 82 | 83 | // replace a span, but embed elsewhere 84 | if (this.tagName == 'SPAN') { 85 | var previd = this.id; 86 | $(this).replaceWith($stepper); 87 | if (previd) $stepper.attr('id',previd); 88 | } 89 | else { 90 | $(this).append($stepper); 91 | } 92 | 93 | // hook to call functions on this object 94 | $stepper[0].ui = { 95 | 96 | refresh: function() { 97 | this.updateNumber(); 98 | this._drawArrow(0, 1); 99 | this._drawArrow(1, 1); 100 | return this; 101 | }, 102 | 103 | _drawArrow: function(i,state) { 104 | var $elm = $(arrow[i]), 105 | desc = $.jstepperArrows[i], 106 | fillStyle = (state == 2) ? hcolor : (state == 3) ? acolor : color, 107 | draw = { fillStyle: fillStyle }, 108 | w = $elm.width(), h = $elm.height(); 109 | 110 | if (w <= 0) w = $elm.attr('width'); 111 | if (h <= 0) h = $elm.attr('height'); 112 | 113 | $.each(desc.poly,function(i,v){ 114 | ++i; 115 | draw['x'+i] = v[0] * w; 116 | draw['y'+i] = v[1] * h; 117 | }); 118 | $elm.restoreCanvas().clearCanvas().drawLine(draw); 119 | }, 120 | 121 | updateNumber: function() { 122 | $marq.html((active || placeholder === null) ? val.toString() : placeholder); 123 | return this; 124 | }, 125 | 126 | _doclick: function(i) { 127 | this.add(i ? 1 : -1); 128 | this._drawArrow(i, 3); 129 | var self = this; 130 | setTimeout(function(){ self._drawArrow(i, 2); }, 50); 131 | }, 132 | 133 | add: function(x) { 134 | val = (((val - min) + x + span) % span) + min; 135 | this.updateNumber(); 136 | this.didChange(val); 137 | return this; 138 | }, 139 | 140 | min: function(v) { 141 | if (typeof v === un) return min; 142 | this.setRange(v,max); 143 | return this; 144 | }, 145 | 146 | max: function(v) { 147 | if (typeof v === un) return max; 148 | this.setRange(min,v); 149 | return this; 150 | }, 151 | 152 | val: function(v) { 153 | if (typeof v === un) return val; 154 | val = (((v - min) + span) % span) + min; 155 | this.updateNumber(); 156 | return this; 157 | }, 158 | 159 | setRange: function(lo, hi, ini) { 160 | if (lo > hi) hi = (lo += hi -= lo) - hi; 161 | min = lo; max = hi; span = hi - lo + 1; 162 | if (typeof ini !== un) val = ini; 163 | if (val < min) val = min; 164 | if (val > max) val = max; 165 | this.updateNumber(); 166 | return this; 167 | }, 168 | 169 | active: function(a) { 170 | if (typeof a === un) return active; 171 | active = a; 172 | $marq.toggleClass('inactive', !a); 173 | this.updateNumber(); 174 | return this; 175 | }, 176 | 177 | disable: function() { this.active(false); return this; }, 178 | enable: function() { this.active(true); return this; }, 179 | 180 | clearPlaceholder: function() { 181 | this.setPlaceholder(null); 182 | return this; 183 | }, 184 | setPlaceholder: function(p) { 185 | placeholder = p; 186 | if (!active) this.updateNumber(); 187 | return this; 188 | }, 189 | 190 | didChange: onChange 191 | 192 | }; 193 | 194 | // set hover and click for each arrow 195 | $.each($.jstepperArrows, function(i,desc) { 196 | var $elm = $(arrow[i]), 197 | w = arrowWidth ? arrowWidth : $elm.width() ? $elm.width() : 15, 198 | h = arrowHeight ? arrowHeight : $elm.height() ? $elm.height() : 24; 199 | $elm[0]._index = i; 200 | $elm 201 | .css({float:'left'}) 202 | .attr({width:w,height:h,'class':desc.name}) 203 | .hover( 204 | function(e) { $stepper[0].ui._drawArrow(e.target._index, 2); }, 205 | function(e) { $stepper[0].ui._drawArrow(e.target._index, 1); } 206 | ) 207 | .click(function(e){ 208 | $stepper[0].ui._doclick(e.target._index); 209 | return false; 210 | }); 211 | }); 212 | 213 | // init the visuals first time 214 | $stepper[0].ui.refresh(); 215 | 216 | }); // this.each 217 | 218 | }; // $.fn.jstepper 219 | 220 | })( jQuery ); 221 | 222 | -------------------------------------------------------------------------------- /abm/js/marlin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/marlin.js - Functions to parse, read, and write Marlin configs. 4 | */ 5 | 6 | const path = require('path'), 7 | fs = require('fs'), 8 | os = require('os'), 9 | schema = require('./schema'); 10 | 11 | const vscode = require('vscode'), ws = vscode.workspace, 12 | workspaceRoot = (ws && ws.workspaceFolders && ws.workspaceFolders.length) ? ws.workspaceFolders[0].uri.fsPath : ''; 13 | 14 | var temp = {}, bugme = false; 15 | 16 | function init(b) { bugme = b; } 17 | 18 | // 19 | // Files to parse for config, version, and build info. 20 | // 21 | var files = { 22 | pins: { name: 'pins.h', path: ['src', 'pins'] }, 23 | boards: { name: 'boards.h', path: ['src', 'core'] }, 24 | config: { name: 'Configuration.h', path: [] }, 25 | config_adv: { name: 'Configuration_adv.h', path: [] }, 26 | version: { name: 'Version.h', path: ['src', 'inc'] } 27 | }; 28 | 29 | // TODO: Use config data from Schema object 30 | 31 | // Get whether a setting is enabled (defined) in a blob of text 32 | function _confDefined(text, optname) { 33 | const find = new RegExp(`^\\s*#define\\s+${optname}\\b`, 'gm'), 34 | r = find.exec(text); 35 | if (bugme) console.log(`${optname} is ${r?'ENABLED':'unset'}`); 36 | return (r !== null); // Any match means it's uncommented 37 | } 38 | 39 | // Get whether a setting is enabled (defined) in one or both config files 40 | function confDefined(fileid, optname) { return _confDefined(files[fileid].text, optname); } 41 | function configDefined(optname) { return confDefined('config', optname); } 42 | function configAdvDefined(optname) { return confDefined('config_adv', optname); } 43 | function configAnyDefined(optname) { 44 | return configDefined(optname) ? true : configAdvDefined(optname); 45 | } 46 | 47 | // Get a single config value by scraping the given text 48 | function _confValue(text, optname) { 49 | var val = ''; 50 | const find = new RegExp(`^\\s*#define\\s+${optname}\\s+(.+)`, 'gm'), 51 | r = find.exec(text); 52 | if (r) val = r[1].replace(/\s*(\/\/.*)?$/, ''); 53 | return val; 54 | } 55 | 56 | // Get the value of a single config option, searching one or both config files 57 | function confValue(fileid, optname) { return _confValue(files[fileid].text, optname); } 58 | function configValue(optname) { return confValue('config', optname); } 59 | function configAdvValue(optname) { return confValue('config_adv', optname); } 60 | function configAnyValue(optname) { 61 | if (configDefined(optname)) return configValue(optname) 62 | if (configAdvDefined(optname)) return configAdvValue(optname); 63 | } 64 | 65 | // 66 | // Return a path object for Marlin/parts[0]/parts[1]/... 67 | // 68 | function pathFromArray(parts) { 69 | return path.join(workspaceRoot, 'Marlin', parts.join(path.sep)); 70 | } 71 | 72 | // 73 | // Get the full path to the Marlin file for a given file description 74 | // 75 | function fullPathForFileDesc(f) { 76 | if (!f.fullpath) { 77 | var p = f.path; p.push(f.name); 78 | f.fullpath = pathFromArray(p); 79 | } 80 | return f.fullpath; 81 | } 82 | 83 | const filePathForID = (fileid) => fullPathForFileDesc(files[fileid]); 84 | 85 | // 86 | // Watch files for changes 87 | // 88 | var watchers = []; 89 | function unwatchConfigurations() { 90 | watchers.forEach((w) => { w.close() }); 91 | watchers = []; 92 | } 93 | 94 | function watchConfigurations(handler) { 95 | unwatchConfigurations(); 96 | watchers = [ 97 | fs.watch(filePathForID('config'), {}, handler), 98 | fs.watch(filePathForID('config_adv'), {}, handler) 99 | ]; 100 | } 101 | 102 | // 103 | // Check for valid Marlin files 104 | // 105 | function validate() { 106 | if (workspaceRoot == '') return { ok: false, error: 'Error: No folder is open.' }; 107 | for (const k in files) { // Iterate keys 108 | const err = fileCheck(k); 109 | if (err) 110 | return { ok: false, error: `Error: ${files[k].name} ${err}.` }; 111 | } 112 | return { ok: true }; 113 | } 114 | 115 | function watchAndValidate(handler) { 116 | unwatchConfigurations(); 117 | if (workspaceRoot == '') return; 118 | const marlin_path = path.join(workspaceRoot, 'Marlin'); 119 | if (fs.existsSync(marlin_path)) 120 | watchers = [ fs.watch(marlin_path, {}, handler) ]; 121 | } 122 | 123 | // 124 | // Verify that one of Marlin's files exists 125 | // 126 | function fileCheck(fileid) { 127 | var file_issue; 128 | try { 129 | fs.accessSync(filePathForID(fileid), fs.constants.F_OK | fs.constants.W_OK); 130 | } catch (err) { 131 | file_issue = err.code === 'ENOENT' ? 'is missing' : 'is read-only'; 132 | } 133 | return file_issue; 134 | } 135 | 136 | // 137 | // Read a Marlin/ workspace file and cache the contents in files[fileid].text 138 | // If a config group is loading, load asynchronously, and if this is the last file call onSuccess() 139 | // For any read error call onError(). 140 | // 141 | function readMarlinFileContents(fileid, onSuccess, onError) { 142 | 143 | const mf_path = filePathForID(fileid); 144 | const filespec = files[fileid]; 145 | 146 | if (bugme) console.log(`Reading... ${mf_path}`); 147 | 148 | if (temp.filesToLoad) { 149 | fs.readFile(mf_path, (err, data) => { 150 | if (err) 151 | onError(err, `Couldn't read ${filespec.name}`); 152 | else { 153 | filespec.text = data.toString(); 154 | if (--temp.filesToLoad == 0) onSuccess(); 155 | } 156 | }); 157 | } 158 | else { 159 | try { 160 | // Use node.js to read the file 161 | //var data = fs.readFileSync(mf_path); 162 | filespec.text = fs.readFileSync(mf_path, {encoding:'utf8'}); 163 | } catch (err) { 164 | onError(err, `Couldn't read ${filespec.name}`); 165 | } 166 | } 167 | } 168 | 169 | // 170 | // Reload all parsed files and run success/error callback 171 | // 172 | function refreshAll(onSuccess, onError) { 173 | temp.filesToLoad = Object.keys(files).length; 174 | for (const fid in files) // Iterate keys 175 | readMarlinFileContents(fid, onSuccess, onError); 176 | } 177 | 178 | // 179 | // - Get Marlin version and distribution date 180 | // 181 | var version_info; 182 | function extractVersionInfo() { 183 | version_info = { 184 | vers: _confValue(files.version.text, 'SHORT_BUILD_VERSION').dequote(), 185 | date: _confValue(files.version.text, 'STRING_DISTRIBUTION_DATE').dequote(), 186 | auth: _confValue(files.config.text, 'STRING_CONFIG_H_AUTHOR').dequote() 187 | }; 188 | return version_info; 189 | } 190 | 191 | // 192 | // Extract temperature sensor options from Configuration.h 193 | // 194 | var temp_sensor_desc; 195 | function extractTempSensors() { 196 | 197 | //pv.postMessage({ command:'text', text:files.config.text }); 198 | 199 | // Get all the thermistors and save them into an object 200 | const findAll = new RegExp('^\\s*\\*\\s*Temperature sensors .+$([\\s\\S]+\\*\\/)', 'gm'), 201 | findEach = new RegExp('^\\s*\\*\\s*(-?\\d+)\\s*:\\s*(.+)$', 'gm'), 202 | r = findAll.exec(files.config.text); 203 | 204 | var out = {}; 205 | let s; 206 | while (s = findEach.exec(r[1])) out[s[1]] = { desc: s[2] }; 207 | 208 | temp_sensor_desc = out; 209 | return temp_sensor_desc; 210 | } 211 | 212 | // 213 | // - Get pins file, archs, and envs for a board from pins.h. 214 | // - If the board isn't found, look for a rename alert. 215 | // - Get the status of environment builds. 216 | // 217 | // Return hashed array { mb, pins_files, archs, archs_arr, envs, short, description, (has_debug), (error) } 218 | // 219 | var board_info; 220 | function extractBoardInfo(mb) { 221 | let r, out = { has_debug: false }, sb = mb.replace('BOARD_', ''); 222 | 223 | // Get the include line matching the board 224 | const lfind = new RegExp(`if\\s*MB\\(.*\\b${sb}\\b.*\\).*\n\\s*(#include.+)\n`, 'g'); 225 | if ((r = lfind.exec(files.pins.text))) { 226 | 227 | let inc_line = r[1]; 228 | 229 | // mb: MOTHERBOARD name 230 | out.mb = mb; 231 | 232 | // pins_files: Pins files relative paths 233 | out.pins_files = []; 234 | 235 | const pinfind = new RegExp(`#include\\s+"((.+/)?pins_.+)"`, 'g'); 236 | let short_pins_path = inc_line.replace(/.*#include\s+"([^"]*)".*/, '$1'); 237 | while (short_pins_path) { 238 | out.pins_files.push({ 239 | text: short_pins_path, 240 | uri: path.join('Marlin', 'src', 'pins', short_pins_path) 241 | }); 242 | const pinfile_path = pathFromArray(['src', 'pins', ...short_pins_path.split('/')]); 243 | const pinfile_dir = short_pins_path.replace(/\/pins_.+\.h$/, ''); 244 | short_pins_path = null; 245 | if (fs.existsSync(pinfile_path)) { 246 | const pinfile_text = fs.readFileSync(pinfile_path, {encoding:'utf8'}); 247 | if ((r = pinfind.exec(pinfile_text))) { 248 | short_pins_path = `${pinfile_dir}/${r[1]}`.replace(/[^\/]+\/\.\.\//, ''); 249 | } 250 | } 251 | } 252 | 253 | // archs: The architecture(s) for the board 254 | out.archs = inc_line.replace(/.+\/\/\s*((\w+\s*)+(,\s*\w+\s*)*)(env|mac|win|lin|uni):.+/, '$1').trim(); 255 | out.archs_arr = out.archs.split(/\s*,\s*/); 256 | 257 | // envs: The environments for the board 258 | out.envs = []; 259 | 260 | const efind = /(env|mac|win|lin|uni):(\w+)/g, 261 | platMap = { win32: 'win', darwin: 'mac', linux: 'lin' }, 262 | platform = platMap[process.platform] || 'uni'; 263 | 264 | let match; 265 | while ((match = efind.exec(inc_line))) { 266 | const [_full, type, name] = match; 267 | 268 | // Skip if the current platform doesn't match 269 | if (type !== 'env' && type !== platform) continue; 270 | 271 | let note = ''; 272 | if (/STM32F....E/i.test(name)) note = '(512K)'; 273 | else if (/STM32F....C/i.test(name)) note = '(256K)'; 274 | 275 | const debugenv = /_debug$/i.test(name); 276 | out.envs.push({ name, note, debug: debugenv, native: type !== 'env' }); 277 | if (debugenv) out.has_debug = true; 278 | } 279 | 280 | // Get the description from the boards.h file 281 | const cfind = new RegExp(`#define\\s+${mb}\\s+\\d+\\s*//(.+)`, 'gm'); 282 | r = cfind.exec(files.boards.text); 283 | out.description = r ? r[1].trim() : ''; 284 | } 285 | else { 286 | const ofind = new RegExp(`#error\\s+"(${mb} is no longer [^.]+)`, 'g'), 287 | bfind = new RegExp(`#error\\s+"(${mb} (has been renamed|is now) [^.]+)`, 'g'); 288 | if ((r = ofind.exec(files.pins.text))) { 289 | out.error = r[1]; 290 | out.short = `Unsupported MOTHERBOARD`; 291 | } 292 | else if ((r = bfind.exec(files.pins.text))) { 293 | // TODO: Show a "Fix" button to update an old board name. 294 | out.error = r[1]; 295 | out.fix = `fixboard("${mb}")`; 296 | } 297 | else if (!mb.startsWith('BOARD_')) { 298 | out.error = "MOTHERBOARD name missing 'BOARD_'"; 299 | out.short = "Missing 'BOARD_'?"; 300 | } 301 | else { 302 | out.error = `Unknown MOTHERBOARD ${mb}`; 303 | out.short = `Unknown MOTHERBOARD`; 304 | } 305 | } 306 | 307 | board_info = out; 308 | return board_info; 309 | } 310 | 311 | // 312 | // Get the type of geometry and stuff like that 313 | // Return hashed array { name, style, dimensions, description, heated_bed, (bed_sensor) } 314 | // 315 | var machine_info; 316 | function getMachineSettings() { 317 | var out = {}; 318 | 319 | out.name = configValue('CUSTOM_MACHINE_NAME').dequote(); 320 | 321 | const mtypes = [ 322 | 'DELTA', 323 | 'MORGAN_SCARA', 'MP_SCARA', 'AXEL_TPARA', 324 | 'COREXY', 'COREXZ', 'COREYZ', 'COREYX', 'COREZX', 'COREZY', 325 | 'MARKFORGED_XY', 'MARKFORGED_YX', 326 | 'ARTICULATED_ROBOT_ARM', 'FOAMCUTTER_XYUV', 'POLAR' 327 | ]; 328 | const mpretty = [ 329 | 'Delta', 330 | 'Morgan SCARA', 'MP SCARA', 'Axel TPARA', 331 | 'CoreXY', 'CoreXZ', 'CoreYZ', 'CoreYX', 'CoreZX', 'CoreZY', 332 | 'Markforged XY', 'Markforged YX', 333 | 'Robot Arm', 'Foam Cutter', 'Polar' 334 | ]; 335 | 336 | const s = out.style = mtypes 337 | .map((v, i) => (configDefined(v) ? mpretty[i] : null)) 338 | .find(s => s !== null) || 'Cartesian'; 339 | 340 | const d = out.dimensions = { x: configValue('X_BED_SIZE'), y: configValue('Y_BED_SIZE'), z: configValue('Z_MAX_POS') }; 341 | 342 | out.description = `${s} ${d.x}x${d.y}x${d.z}mm`; 343 | 344 | const bed_sensor = configValue('TEMP_SENSOR_BED'); 345 | out.heated_bed = !!(1 * bed_sensor); 346 | if (out.heated_bed) { 347 | out.bed_sensor = bed_sensor; 348 | out.description += ` with Heated Bed [ ${bed_sensor} ]`; 349 | } 350 | else 351 | out.description += ' (no Heated Bed)'; 352 | 353 | machine_info = out; 354 | return machine_info; 355 | } 356 | 357 | // 358 | // Get the number of EXTRUDERS and related options 359 | // Return hashed array { extruders, diam, (sensors[]), (sensor_err), type, fancy, description } 360 | // 361 | var extruder_info; 362 | function getExtruderSettings() { 363 | var out = {}; 364 | const extruders = configValue('EXTRUDERS') * 1; 365 | 366 | out.extruders = extruders; 367 | out.diam = configValue('DEFAULT_NOMINAL_FILAMENT_DIA'); 368 | 369 | // Get the extruder temp sensors 370 | out.sensors = []; 371 | for (let i = 0; i < extruders; i++) 372 | if (!(out.sensors[i] = configValue(`TEMP_SENSOR_${i}`))) 373 | out.sensor_err = true; 374 | 375 | if (extruders == 1 && configDefined('TEMP_SENSOR_1_AS_REDUNDANT')) 376 | out.sensors[1] = configValue('TEMP_SENSOR_1'); 377 | 378 | // Only one of these types is allowed at a time 379 | const epretty = [ 'Single Nozzle' ]; 380 | schema.ConfigSchema.exclusive.toolhead.every((v,i) => { 381 | if (!configDefined(v)) return true; 382 | out.type = epretty[i] ?? v.toLabel(); 383 | return false; 384 | }); 385 | 386 | // These are mutually-exclusive 387 | const efancy = [ 'MIXING_EXTRUDER', 'SWITCHING_EXTRUDER', 'SWITCHING_NOZZLE', 'MK2_MULTIPLEXER', 'PRUSA_MMU2' ]; 388 | efancy.every((v) => { 389 | if (!configDefined(v)) return true; 390 | out.fancy = v.toLabel(); 391 | }); 392 | 393 | if (out.fancy) { 394 | let e = extruders; 395 | if (out.fancy == 'Mixing Extruder') 396 | e = out.steppers = configValue('MIXING_STEPPERS'); 397 | out.fancy += ` (${e} channels)`; 398 | } 399 | 400 | if (!out.type && !out.fancy) { 401 | switch (out.extruders) { 402 | case 1: out.description = '(Single Extruder)'; break; 403 | case 2: out.description = '(Dual Extruder)'; break; 404 | default: out.description = out.extruders + ' Extruders'; break; 405 | } 406 | } 407 | else { 408 | if (out.type && out.fancy) 409 | out.description = `${out.type} with ${out.fancy}`; 410 | else if (out.type == 'Single Nozzle') 411 | out.description = `Single Nozzle with ${out.extruders} inputs`; 412 | else if (out.type) 413 | out.description = `${out.type} with ${out.extruders}`; 414 | else 415 | out.description = out.fancy; 416 | } 417 | 418 | out.description += " [ " + out.sensors.join(', ') + " ]"; 419 | 420 | extruder_info = out; 421 | return extruder_info; 422 | } 423 | 424 | // 425 | // Re-fetch information from the board's pins file(s) 426 | // and re-load the pins file contents into files.pindef.text 427 | // Return hashed array with { name, path, mb } 428 | // 429 | var pindef_info; 430 | function getPinDefinitionInfo(mb) { 431 | if (!files.pindef || files.pindef.mb != mb) { 432 | const pbits = `src/pins/${board_info.pins_files[0].text}`.split('/'); 433 | files.pindef = { name: pbits.pop(), path: pbits, mb: mb }; 434 | readMarlinFileContents('pindef'); // Since temp.filesToLoad == 0 just read the file 435 | } 436 | 437 | pindef_info = { 438 | board_name: confValue('pindef', 'BOARD_INFO_NAME').dequote() 439 | }; 440 | 441 | return pindef_info; 442 | } 443 | 444 | module.exports = { 445 | workspaceRoot, pathFromArray, 446 | 447 | files, init, validate, refreshAll, 448 | 449 | watchConfigurations, watchAndValidate, 450 | 451 | configDefined, 452 | configValue, confValue, _confValue, 453 | 454 | extractVersionInfo, extractTempSensors, extractBoardInfo, 455 | getMachineSettings, getExtruderSettings, getPinDefinitionInfo 456 | }; 457 | -------------------------------------------------------------------------------- /abm/js/vsview.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * abm/js/vsview.js 4 | * 5 | * Provide some VSCode hooks 6 | */ 7 | 8 | const vscode = acquireVsCodeApi(); 9 | 10 | // Messages sent by buttons back to the view controller abm.handleMessageFromUI(m) 11 | function _msg(m) { vscode.postMessage(m); } 12 | -------------------------------------------------------------------------------- /abm/pane/geom.html: -------------------------------------------------------------------------------- 1 |
2 |

Geometry Panel

3 |
Geometry Panel Under Construction
4 |
5 |
6 | -------------------------------------------------------------------------------- /abm/pane/lcd.html: -------------------------------------------------------------------------------- 1 |
2 |

LCD Panel

3 |
LCD Panel Under Construction
4 |
5 |
6 | -------------------------------------------------------------------------------- /abm/pane/sd.html: -------------------------------------------------------------------------------- 1 |
2 |

SD Panel

3 |
SD Panel Under Construction
4 |
5 |
6 | -------------------------------------------------------------------------------- /abm/prefs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * prefs.js 4 | * Getters and setters for extension preferences. 5 | */ 6 | 7 | const vscode = require("vscode"), ws = vscode.workspace; 8 | 9 | function settings() { return ws.getConfiguration('auto-build'); } 10 | 11 | function _get_setting(set, name, def=false) { return set.get(name, def); } 12 | function get_setting(name, def=false) { return _get_setting(settings(), name, def); } 13 | 14 | function _set_setting(set, name, val) { 15 | set.update(name, val, set.inspect(name).workspaceValue == undefined); 16 | } 17 | function set_setting(name, val) { _set_setting(settings(), name, val); } 18 | 19 | const m = module.exports; 20 | 21 | m.show_on_startup = () => { return get_setting('showOnStart'); } 22 | m.preserve_pio = () => { return get_setting('preservePIO'); } 23 | m.reuse_terminal = () => { return get_setting('build.reuseTerminal', true); } 24 | m.silent_build = () => { return get_setting('build.silent'); } 25 | m.auto_reveal = () => { return get_setting('build.reveal', true); } 26 | 27 | m.default_env = () => { return get_setting('defaultEnv.name', ''); } 28 | m.default_env_update = () => { return get_setting('defaultEnv.update', true); } 29 | 30 | m.set_show_on_startup = (sh) => { set_setting('showOnStart', sh); } 31 | m.set_silent_build = (bs) => { set_setting('build.silent', bs); } 32 | m.set_auto_reveal = (br) => { set_setting('build.reveal', br); } 33 | m.set_default_env = (e) => { set_setting('defaultEnv.name', e); } 34 | 35 | m.set_preserve_pio = (p) => { set_setting('preservePIO', p); } 36 | 37 | m.set_pio_open_ini = (o) => { 38 | const pio_set = vscode.workspace.getConfiguration('platformio-ide'); 39 | _set_setting(pio_set, 'autoOpenPlatformIOIniFile', o); 40 | } 41 | -------------------------------------------------------------------------------- /extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * extension.js 4 | * Extension entry point. Runs when the extension is activated. 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const vscode = require('vscode'), 10 | abm = require('./abm/abm'), 11 | prefs = require('./abm/prefs'), 12 | format = require('./abm/format'), 13 | info = require('./abm/info'), 14 | editor = require('./abm/editor'); 15 | 16 | exports.activate = (context) => { 17 | 18 | const vc = vscode.commands, 19 | cs = context.subscriptions; 20 | 21 | cs.push( 22 | vc.registerCommand('abm.build', () => { abm.run_command('build'); }), 23 | vc.registerCommand('abm.upload', () => { abm.run_command('upload'); }), 24 | vc.registerCommand('abm.traceback', () => { abm.run_command('traceback'); }), 25 | vc.registerCommand('abm.clean', () => { abm.run_command('clean'); }), 26 | vc.registerCommand('abm.config', () => { abm.run_command('config'); }), 27 | vc.registerCommand('abm.show', () => { abm.run_command(); }), 28 | vc.registerCommand('abm.edit.base', () => { abm.edit_config('base'); }), 29 | vc.registerCommand('abm.edit.adv', () => { abm.edit_config('adv'); }), 30 | vc.registerCommand('abm.sponsor', () => { abm.sponsor(); }), 31 | vc.registerCommand('abm.codeformat', () => { format.codeformat(); }), 32 | vc.registerCommand('abm.export.json', () => { abm.run_schema_py('json'); }), 33 | vc.registerCommand('abm.export.yml', () => { abm.run_schema_py('yml'); }), 34 | vc.registerCommand('abm.apply.ini', () => { abm.run_configuration_py(); }), 35 | 36 | // Register a webview provider for the Info panel 37 | info.InfoPanelProvider.register(context), 38 | 39 | // Formatter to do an extra level of indentation for Marlin C++. 40 | format.PPFormatProvider.register(context), 41 | 42 | // Register a custom editor provider for Configuration files 43 | editor.ConfigEditorProvider.register(context) 44 | ); 45 | 46 | abm.init(context); // Init the abm module before use 47 | abm.validate(); // Validate the workspace for ABM 48 | abm.watchAndValidate(); // Watch files and folders for changes to update the status 49 | abm.set_context('active', true); // Tell VSCode the status to update the UI 50 | 51 | // No one actually wants platformio.ini to open, unless they do 52 | if (!prefs.preserve_pio()) prefs.set_pio_open_ini(false); 53 | 54 | // Show the panel with no command 55 | if (prefs.show_on_startup()) abm.run_command(); 56 | 57 | }; // activate 58 | 59 | exports.deactivate = () => { abm.set_context('active', false); }; 60 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/icon.png -------------------------------------------------------------------------------- /img/AB_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/AB_icon.png -------------------------------------------------------------------------------- /img/AB_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/AB_menu.png -------------------------------------------------------------------------------- /img/Activity_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/Activity_bar.png -------------------------------------------------------------------------------- /img/B_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/B_small.png -------------------------------------------------------------------------------- /img/C_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/C_small.png -------------------------------------------------------------------------------- /img/K_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/K_small.png -------------------------------------------------------------------------------- /img/T_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/T_small.png -------------------------------------------------------------------------------- /img/U_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/U_small.png -------------------------------------------------------------------------------- /img/abm-envs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/abm-envs.png -------------------------------------------------------------------------------- /img/config-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarlinFirmware/AutoBuildMarlin/33e41eaf894f690d3260bf4f715d4bdb07f88506/img/config-editor.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auto-build", 3 | "displayName": "Auto Build Marlin", 4 | "description": "Provides an interface to configure, build, and upload Marlin Firmware.", 5 | "version": "2.1.74", 6 | "preview": false, 7 | "publisher": "marlinfirmware", 8 | "icon": "icon.png", 9 | "sponsor": { 10 | "url": "https://marlinfw.org/donate" 11 | }, 12 | "badges": [ 13 | { 14 | "url": "https://vsmarketplacebadges.dev/version-short/marlinfirmware.auto-build.png", 15 | "href": "https://marketplace.visualstudio.com/items?itemName=marlinfirmware.auto-build", 16 | "description": "VS Marketplace" 17 | }, 18 | { 19 | "url": "https://vsmarketplacebadges.dev/installs-short/marlinfirmware.auto-build.png", 20 | "href": "https://marketplace.visualstudio.com/items?itemName=marlinfirmware.auto-build", 21 | "description": "installs" 22 | }, 23 | { 24 | "url": "https://img.shields.io/github/issues/MarlinFirmware/AutoBuildMarlin.png", 25 | "href": "https://github.com/MarlinFirmware/AutoBuildMarlin/issues", 26 | "description": "GitHub issues" 27 | } 28 | ], 29 | "keywords": [ 30 | "3DPrinting", 31 | "Firmware", 32 | "PlatformIO", 33 | "Embedded", 34 | "AVR", 35 | "ARM" 36 | ], 37 | "engines": { 38 | "vscode": "^1.63.0" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/MarlinFirmware/AutoBuildMarlin" 43 | }, 44 | "homepage": "https://github.com/MarlinFirmware/AutoBuildMarlin/blob/master/README.md", 45 | "markdown": "github", 46 | "extensionKind": [ 47 | "ui" 48 | ], 49 | "categories": [ 50 | "Formatters", 51 | "Other" 52 | ], 53 | "activationEvents": [ 54 | "workspaceContains:/Marlin/Configuration.h" 55 | ], 56 | "main": "./extension", 57 | "contributes": { 58 | "customEditors": [ 59 | { 60 | "viewType": "abm.configEditor", 61 | "displayName": "Config Editor", 62 | "selector": [ 63 | { 64 | "filenamePattern": "**/Marlin/Configuration.h" 65 | }, 66 | { 67 | "filenamePattern": "**/Marlin/Configuration_adv.h" 68 | } 69 | ], 70 | "priority": "option" 71 | } 72 | ], 73 | "viewsContainers": { 74 | "activitybar": [ 75 | { 76 | "id": "autoBuildVC", 77 | "title": "Auto Build Marlin", 78 | "icon": "resources/AB.svg" 79 | } 80 | ] 81 | }, 82 | "configuration": { 83 | "type": "object", 84 | "title": "Auto Build Marlin", 85 | "properties": { 86 | "auto-build.build.reuseTerminal": { 87 | "type": "boolean", 88 | "default": true, 89 | "description": "Reuse the same Terminal for all commands." 90 | }, 91 | "auto-build.showOnStart": { 92 | "type": "boolean", 93 | "default": true, 94 | "markdownDescription": "Open the **Auto Build Marlin** panel on startup." 95 | }, 96 | "auto-build.preservePIO": { 97 | "type": "boolean", 98 | "default": false, 99 | "markdownDescription": "Allow PlatformIO to open `platformio.ini` on startup. Enable this to leave its settings unaltered." 100 | }, 101 | "auto-build.build.silent": { 102 | "type": "boolean", 103 | "default": true, 104 | "description": "Suppress build messages, only showing warnings and errors." 105 | }, 106 | "auto-build.build.reveal": { 107 | "type": "boolean", 108 | "default": true, 109 | "description": "On successful build, reveal and select the HEX / BIN in the system file browser." 110 | }, 111 | "auto-build.defaultEnv.name": { 112 | "type": "string", 113 | "description": "Default environment for sidebar ABM build/upload shortcuts. May be updated by build/upload from the ABM panel." 114 | }, 115 | "auto-build.defaultEnv.update": { 116 | "type": "boolean", 117 | "default": true, 118 | "description": "Update the default environment on build/upload from the ABM panel." 119 | } 120 | } 121 | }, 122 | "views": { 123 | "autoBuildVC": [ 124 | { 125 | "id": "abm.welcomeView", 126 | "name": "Build…", 127 | "visibility": "visible" 128 | }, 129 | { 130 | "type": "webview", 131 | "id": "abm.infoView", 132 | "name": "Marlin Info" 133 | } 134 | ] 135 | }, 136 | "viewsWelcome": [ 137 | { 138 | "view": "abm.welcomeView", 139 | "contents": "%view.workbench.abm.inactive%", 140 | "when": "!abm.inited" 141 | }, 142 | { 143 | "view": "abm.welcomeView", 144 | "contents": "%view.workbench.abm.intro%", 145 | "when": "abm.inited && !abm.err.locate" 146 | }, 147 | { 148 | "view": "abm.welcomeView", 149 | "contents": "%view.workbench.abm.err.empty%", 150 | "when": "abm.inited && workbenchState == empty" 151 | }, 152 | { 153 | "view": "abm.welcomeView", 154 | "contents": "%view.workbench.abm.err.folder%", 155 | "when": "abm.err.locate && workbenchState == folder" 156 | }, 157 | { 158 | "view": "abm.welcomeView", 159 | "contents": "%view.workbench.abm.err.workspace%", 160 | "when": "abm.err.locate && workbenchState == workspace" 161 | }, 162 | { 163 | "view": "abm.welcomeView", 164 | "contents": "%view.workbench.abm.err.parse%", 165 | "when": "abm.err.parse" 166 | } 167 | ], 168 | "commands": [ 169 | { 170 | "category": "Auto Build Marlin", 171 | "command": "abm.show", 172 | "title": "Show ABM Panel", 173 | "icon": { 174 | "light": "resources/AB.svg", 175 | "dark": "resources/AB_dark.svg" 176 | } 177 | }, 178 | { 179 | "category": "Auto Build Marlin", 180 | "command": "abm.build", 181 | "title": "Build", 182 | "icon": { 183 | "light": "resources/B48x48_light.svg", 184 | "dark": "resources/B48x48_dark.svg" 185 | } 186 | }, 187 | { 188 | "category": "Auto Build Marlin", 189 | "command": "abm.upload", 190 | "title": "Upload", 191 | "icon": { 192 | "light": "resources/U48x48_light.svg", 193 | "dark": "resources/U48x48_dark.svg" 194 | } 195 | }, 196 | { 197 | "category": "Auto Build Marlin", 198 | "command": "abm.traceback", 199 | "title": "Upload (traceback)", 200 | "icon": { 201 | "light": "resources/T48x48_light.svg", 202 | "dark": "resources/T48x48_dark.svg" 203 | } 204 | }, 205 | { 206 | "category": "Auto Build Marlin", 207 | "command": "abm.clean", 208 | "title": "Clean", 209 | "icon": { 210 | "light": "resources/C48x48_light.svg", 211 | "dark": "resources/C48x48_dark.svg" 212 | } 213 | }, 214 | { 215 | "category": "Auto Build Marlin", 216 | "command": "abm.config", 217 | "title": "Configure", 218 | "icon": { 219 | "light": "resources/K48x48_light.svg", 220 | "dark": "resources/K48x48_dark.svg" 221 | } 222 | }, 223 | { 224 | "category": "Auto Build Marlin", 225 | "command": "abm.codeformat", 226 | "title": "Format Marlin Code" 227 | }, 228 | { 229 | "category": "Auto Build Marlin", 230 | "command": "abm.export.json", 231 | "title": "Export schema.json" 232 | }, 233 | { 234 | "category": "Auto Build Marlin", 235 | "command": "abm.export.yml", 236 | "title": "Export schema.yml" 237 | }, 238 | { 239 | "category": "Auto Build Marlin", 240 | "command": "abm.apply.ini", 241 | "title": "Apply config.ini" 242 | }, 243 | { 244 | "category": "Auto Build Marlin", 245 | "command": "abm.edit.base", 246 | "title": "Edit Configuration.h" 247 | }, 248 | { 249 | "category": "Auto Build Marlin", 250 | "command": "abm.edit.adv", 251 | "title": "Edit Configuration_adv.h" 252 | } 253 | ], 254 | "menus": { 255 | "view/title": [ 256 | { 257 | "command": "abm.show", 258 | "group": "navigation@1", 259 | "when": "abm.active && (view == abm.welcomeView || view == abm.infoView)" 260 | }, 261 | { 262 | "command": "abm.build", 263 | "group": "navigation@2", 264 | "when": "abm.active && view == abm.welcomeView" 265 | }, 266 | { 267 | "command": "abm.upload", 268 | "group": "navigation@3", 269 | "when": "abm.active && view == abm.welcomeView" 270 | }, 271 | { 272 | "command": "abm.traceback", 273 | "group": "navigation@4", 274 | "when": "abm.active && abm.has_debug && view == abm.welcomeView" 275 | }, 276 | { 277 | "command": "abm.clean", 278 | "group": "navigation@5", 279 | "when": "abm.active && abm.cleanable && view == abm.welcomeView" 280 | }, 281 | { 282 | "command": "abm.config", 283 | "group": "navigation@6", 284 | "when": "abm.active && (view == abm.welcomeView || view == abm.infoView)" 285 | } 286 | ], 287 | "commandPalette": [ 288 | { 289 | "command": "abm.show", 290 | "when": "abm.active" 291 | }, 292 | { 293 | "command": "abm.build", 294 | "when": "abm.active" 295 | }, 296 | { 297 | "command": "abm.upload", 298 | "when": "abm.active" 299 | }, 300 | { 301 | "command": "abm.traceback", 302 | "when": "abm.active && abm.has_debug" 303 | }, 304 | { 305 | "command": "abm.clean", 306 | "when": "abm.active && abm.cleanable" 307 | }, 308 | { 309 | "command": "abm.config", 310 | "when": "abm.active" 311 | }, 312 | { 313 | "command": "abm.apply.ini", 314 | "when": "abm.active" 315 | }, 316 | { 317 | "command": "abm.codeformat", 318 | "when": "abm.active && editorLangId =~ /c(pp)?/" 319 | } 320 | ] 321 | } 322 | }, 323 | "extensionDependencies": [ 324 | "platformio.platformio-ide" 325 | ], 326 | "devDependencies": { 327 | "@types/vscode": "^1.34.0", 328 | "jslint": "^0.12.1", 329 | "@types/node": "^10.14.17" 330 | }, 331 | "__metadata": { 332 | "id": "aeed6cc8-3e27-4932-8212-161d73cdee2d", 333 | "publisherId": "cc3dab9c-e91d-467b-9b8e-b3d262181451", 334 | "publisherDisplayName": "Marlin Firmware", 335 | "isPreReleaseVersion": false 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /package.nls.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "Auto Build Marlin", 3 | "description": "Quickly build Marlin for your MOTHERBOARD.", 4 | "view.workbench.abm.inactive": "The workspace doesn't contain a (valid) Marlin 2.x project. To use this extension…\n[Open a Marlin 2.x Folder](command:vscode.openFolder)\n…in the workspace.\n[❤ Support Marlin](https://github.com/sponsors/MarlinFirmware)\nIf you find Marlin useful, please consider supporting our ongoing work!\n[Close](command:workbench.action.closeSidebar \"Close the Welcome view\")", 5 | "view.workbench.abm.intro": "Marlin Firmware 2.x is open in the workspace!\n[Show ABM Panel](command:abm.show)\n…or use one of the buttons above to Build, Upload, Clean, etc.\n[Edit Configuration.h](command:abm.edit.base \"Basic machine settings\")\n[Edit Configuration_adv.h](command:abm.edit.adv \"Additional machine settings\")\n…with the Configuration Editor.\n[❤ Support Marlin](command:abm.sponsor)\nIf you find Marlin useful, please consider supporting our ongoing work!\n[Close](command:workbench.action.closeSidebar \"Close the Welcome view\")", 6 | "view.workbench.abm.err.empty": "Nothing open in the workspace. To use this extension…\n[Open a Marlin 2.x Folder](command:vscode.openFolder)\n…in the workspace.\n[❤ Support Marlin](command:abm.sponsor)\nIf you find Marlin useful, please consider supporting our ongoing work!\n[Close](command:workbench.action.closeSidebar \"Close the Welcome view\")", 7 | "view.workbench.abm.err.folder": "The currently open folder doesn't contain a (valid) Marlin 2.x project. To use this extension…\n[Open a Marlin 2.x Folder](command:vscode.openFolder)\n…in the workspace.\n[❤ Support Marlin](command:abm.sponsor)\nIf you find Marlin useful, please consider supporting our ongoing work!\n[Close](command:workbench.action.closeSidebar \"Close the Welcome view\")", 8 | "view.workbench.abm.err.workspace": "The workspace doesn't contain a (valid) Marlin 2.x project. To use this extension…\n[Open a Marlin 2.x Folder](command:vscode.openFolder)\n…in the workspace.\n[❤ Support Marlin](command:abm.sponsor)\nIf you find Marlin useful, please consider supporting our ongoing work!\n[Close](command:workbench.action.closeSidebar \"Close the Welcome view\")", 9 | "view.workbench.abm.err.parse": "Unable to parse Marlin 2.x project files. Repair the project and reload the window." 10 | } 11 | -------------------------------------------------------------------------------- /proto.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto Build Marlin 3 | * proto.js 4 | */ 5 | 6 | 'use strict'; 7 | 8 | function init() { 9 | 10 | // 11 | // Extend String, Number, and Date with extras 12 | // 13 | String.prototype.lpad = function(len, chr=' ') { 14 | if (!len) return this; 15 | var s = this+'', need = len - s.length; 16 | if (need > 0) s = new Array(need+1).join(chr) + s; 17 | return s; 18 | }; 19 | 20 | String.prototype.rpad = function(len, chr=' ') { 21 | if (!len) return this; 22 | var s = this+'', need = len - s.length; 23 | if (need > 0) s += new Array(need+1).join(chr); 24 | return s; 25 | }; 26 | 27 | String.prototype.dequote = function() { return this.replace(/^\s*"|"\s*$/g, '').replace(/\\/g, ''); }; 28 | String.prototype.prePad = function(len, chr=' ') { return len ? this.lpad(len, chr) : this; }; 29 | String.prototype.zeroPad = function(len) { return this.prePad(len, '0'); }; 30 | String.prototype.toHTML = function() { return jQuery('
').text(this).html(); }; 31 | String.prototype.regEsc = function() { return this.replace(/[.?*+^$[\]\\(){}|-]/g, "\\$&"); } 32 | String.prototype.lineCount = function(ind) { var len = (ind === undefined ? this : this.substr(0,ind*1)).split(/\r?\n|\r/).length; return len > 0 ? len - 1 : 0; }; 33 | String.prototype.lines = function() { return this.split(/\r?\n|\r/); }; 34 | String.prototype.line = function(num) { var arr = this.split(/\r?\n|\r/); return num < arr.length ? arr[1*num] : ''; }; 35 | String.prototype.replaceLine = function(num,txt) { var arr = this.split(/\r?\n|\r/); if (num < arr.length) { arr[num] = txt; return arr.join('\n'); } else return this; } 36 | String.prototype.toLabel = function() { return this.replace(/[\[\]]/g, '').replace(/_/g, ' ').toTitleCase(); } 37 | String.prototype.toTitleCase = function() { return this.replace(/([A-Z])(\w+)/gi, function(m,p1,p2) { return p1.toUpperCase() + p2.toLowerCase(); }); } 38 | Number.prototype.limit = function(m1, m2) { 39 | if (m2 == null) return this > m1 ? m1 : this; 40 | return this < m1 ? m1 : this > m2 ? m2 : this; 41 | }; 42 | Date.prototype.fileStamp = function(filename) { 43 | var fs = this.getFullYear() 44 | + ((this.getMonth()+1)+'').zeroPad(2) 45 | + (this.getDate()+'').zeroPad(2) 46 | + (this.getHours()+'').zeroPad(2) 47 | + (this.getMinutes()+'').zeroPad(2) 48 | + (this.getSeconds()+'').zeroPad(2); 49 | 50 | if (filename !== undefined) 51 | return filename.replace(/^(.+)(\.\w+)$/g, '$1-['+fs+']$2'); 52 | 53 | return fs; 54 | } 55 | 56 | } // init 57 | 58 | init(); 59 | -------------------------------------------------------------------------------- /resources/AB.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | AutoBuildMarlin View 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/AB_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | AutoBuildMarlin View 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/B48x48_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/B48x48_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/C48x48_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 20 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/C48x48_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/K48x48_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 12 | 14 | 16 | 19 | 23 | 26 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /resources/K48x48_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 21 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /resources/T48x48_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /resources/T48x48_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /resources/U48x48_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/U48x48_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 16 | 19 | 22 | 23 | 24 | --------------------------------------------------------------------------------